From cf668d37899c1965613d3c6481816e1d80de3c47 Mon Sep 17 00:00:00 2001 From: "Rudy (zarya)" Date: Mon, 23 Jun 2025 20:47:48 +0200 Subject: [PATCH 1/3] Add facilities and layers to the layer editor --- .../templates/maps_feature_form.html | 6 ++ src/backoffice/views/maps.py | 79 ++++++++++++++++++- src/static_src/js/maps/generic/init_loader.js | 16 +++- 3 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/backoffice/templates/maps_feature_form.html b/src/backoffice/templates/maps_feature_form.html index 433bfd864..47d63436b 100644 --- a/src/backoffice/templates/maps_feature_form.html +++ b/src/backoffice/templates/maps_feature_form.html @@ -4,9 +4,15 @@ {% load static %} {% block extra_head %} + + + {{ form.media }} {% leaflet_css plugins="forms" %} {% leaflet_js plugins="forms" %} + + + {{ mapData|json_script:"mapData" }} diff --git a/src/backoffice/views/maps.py b/src/backoffice/views/maps.py index 221f84181..6d0a965a0 100644 --- a/src/backoffice/views/maps.py +++ b/src/backoffice/views/maps.py @@ -20,6 +20,7 @@ from backoffice.forms import MapLayerFeaturesImportForm from camps.mixins import CampViewMixin +from facilities.models import FacilityType from maps.mixins import ExternalLayerMapperViewMixin from maps.mixins import GisTeamViewMixin from maps.mixins import LayerMapperViewMixin @@ -33,16 +34,88 @@ logger = logging.getLogger(f"bornhack.{__name__}") +# ################# Helper ######################## + + +class MapLayerHelper: + """Helper class to not repeat code in the view classes.""" + + camp = None + request = None + + def __init__(self, camp_slug, request): + """Class init method.""" + self.camp_slug = camp_slug + self.request = request + + def get_layers(self) -> QuerySet: + """Method to get the layers the user has access to.""" + user_teams = [] + if not self.request.user.is_anonymous: + user_teams = self.request.user.teammember_set.filter( + team__camp__slug=self.camp_slug, + ).values_list("team__name", flat=True) + return Layer.objects.filter( + ((Q(responsible_team__camp__slug=self.camp_slug) | Q(responsible_team=None)) & Q(public=True)) + | (Q(responsible_team__name__in=user_teams) & Q(public=False)), + ) + + def get_map_data(self) -> dict: + """Method to return the map_data.""" + map_data = { + "grid": static("json/grid.geojson"), + "loggedIn": self.request.user.is_authenticated, + "layers": [], + "facilitytype_list": [], + } + facilitytype_list = FacilityType.objects.filter( + responsible_team__camp__slug=self.camp_slug, + ) + map_data.update({"facilitytype_list": list(facilitytype_list.values())}) + layers = self.get_layers() + map_data.update( + { + "layers": list( + layers.values( + "description", + "name", + "slug", + "uuid", + "icon", + "invisible", + "group__name", + ), + ), + }, + ) + for facility in map_data["facilitytype_list"]: + facility["url"] = reverse( + "facilities:facility_list_geojson", + kwargs={ + "camp_slug": self.camp_slug, + "facility_type_slug": facility["slug"], + }, + ) + for layer in map_data["layers"]: + layer["url"] = reverse( + "maps:map_layer_geojson", + kwargs={"layer_slug": layer["slug"]}, + ) + return map_data + # ################# LAYERS ######################## class MapLayerListView(CampViewMixin, AnyTeamMapperRequiredMixin, ListView): + """View for the list of layers.""" + model = Layer template_name = "maps_layer_list_backoffice.html" context_object_name = "maps_layer_list" def get_context_data(self, **kwargs): + """Method to get the list of layers.""" context = super().get_context_data(**kwargs) context["layers"] = Layer.objects.filter( Q(responsible_team__camp=self.camp) | Q(responsible_team=None), @@ -284,7 +357,8 @@ class MapFeatureCreateView(LayerMapperViewMixin, CreateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["mapData"] = {"grid": static("json/grid.geojson")} + helper = MapLayerHelper(self.kwargs["camp_slug"], self.request) + context.update({"mapData": helper.get_map_data()}) return context def get_form(self, *args, **kwargs): @@ -331,7 +405,8 @@ class MapFeatureUpdateView(LayerMapperViewMixin, UpdateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["mapData"] = {"grid": static("json/grid.geojson")} + helper = MapLayerHelper(self.kwargs["camp_slug"], self.request) + context.update({"mapData": helper.get_map_data()}) return context def get_form(self, *args, **kwargs): diff --git a/src/static_src/js/maps/generic/init_loader.js b/src/static_src/js/maps/generic/init_loader.js index ed1d0e6bd..f961cbeb9 100644 --- a/src/static_src/js/maps/generic/init_loader.js +++ b/src/static_src/js/maps/generic/init_loader.js @@ -1,11 +1,14 @@ const mapData = JSON.parse( document.getElementById('mapData').textContent ); - +const loggedIn = mapData['loggedIn'] var mapObject = undefined; window.addEventListener("map:init", function (event) { var map = event.detail.map; mapObject = new BHMap(map); + mapObject.map.addControl(new L.Control.Fullscreen({ + pseudoFullscreen: true, + })); if (mapData.grid) { mapObject.loadLayer(mapData.grid, "Grid squares", { onEachFeature: mapObject.onEachGrid, @@ -16,6 +19,17 @@ window.addEventListener("map:init", function (event) { }, }); } + if (mapData.facilitytype_list) { + mapData['facilitytype_list'].forEach(function (item) { + mapObject.loadLayer(item.url, item.name, facilityOptions, true, function(){}, "Facilities", item.icon); + }) + } + if (mapData.layers) { + mapData['layers'].forEach(function (item) { + mapObject.loadLayer(item.url, item.name, { + }); + }); + } }); From 774eed9c2deac67a0b8e92ac36c1174ee33832d5 Mon Sep 17 00:00:00 2001 From: "Rudy (zarya)" Date: Mon, 23 Jun 2025 21:02:43 +0200 Subject: [PATCH 2/3] Add the feature colors --- src/static_src/js/maps/generic/init_loader.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/static_src/js/maps/generic/init_loader.js b/src/static_src/js/maps/generic/init_loader.js index f961cbeb9..598a20ec1 100644 --- a/src/static_src/js/maps/generic/init_loader.js +++ b/src/static_src/js/maps/generic/init_loader.js @@ -27,6 +27,16 @@ window.addEventListener("map:init", function (event) { if (mapData.layers) { mapData['layers'].forEach(function (item) { mapObject.loadLayer(item.url, item.name, { + onEachFeature: function(feature, layer) { + if (feature.properties.color !== "#FFFFFFFF") { + layer.setStyle({ + color: feature.properties.color + }) + } + }, + style: { + color: "{{ layer.color }}" + } }); }); } From 652063b0c45590478b8419c612c5c44cc249c5b9 Mon Sep 17 00:00:00 2001 From: "Rudy (zarya)" Date: Thu, 26 Jun 2025 08:22:34 +0200 Subject: [PATCH 3/3] Move helper to mixin --- src/backoffice/views/maps.py | 83 +++--------------------------------- src/maps/mixins.py | 71 ++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 78 deletions(-) diff --git a/src/backoffice/views/maps.py b/src/backoffice/views/maps.py index 6d0a965a0..66db57cf5 100644 --- a/src/backoffice/views/maps.py +++ b/src/backoffice/views/maps.py @@ -9,7 +9,6 @@ from django.contrib.gis.geos import GEOSGeometry from django.db.models import Q from django.http import HttpResponseRedirect -from django.templatetags.static import static from django.urls import reverse from django.views.generic import ListView from django.views.generic.edit import CreateView @@ -20,9 +19,9 @@ from backoffice.forms import MapLayerFeaturesImportForm from camps.mixins import CampViewMixin -from facilities.models import FacilityType from maps.mixins import ExternalLayerMapperViewMixin from maps.mixins import GisTeamViewMixin +from maps.mixins import LayerMapMixin from maps.mixins import LayerMapperViewMixin from maps.models import ExternalLayer from maps.models import Feature @@ -34,76 +33,6 @@ logger = logging.getLogger(f"bornhack.{__name__}") -# ################# Helper ######################## - - -class MapLayerHelper: - """Helper class to not repeat code in the view classes.""" - - camp = None - request = None - - def __init__(self, camp_slug, request): - """Class init method.""" - self.camp_slug = camp_slug - self.request = request - - def get_layers(self) -> QuerySet: - """Method to get the layers the user has access to.""" - user_teams = [] - if not self.request.user.is_anonymous: - user_teams = self.request.user.teammember_set.filter( - team__camp__slug=self.camp_slug, - ).values_list("team__name", flat=True) - return Layer.objects.filter( - ((Q(responsible_team__camp__slug=self.camp_slug) | Q(responsible_team=None)) & Q(public=True)) - | (Q(responsible_team__name__in=user_teams) & Q(public=False)), - ) - - def get_map_data(self) -> dict: - """Method to return the map_data.""" - map_data = { - "grid": static("json/grid.geojson"), - "loggedIn": self.request.user.is_authenticated, - "layers": [], - "facilitytype_list": [], - } - facilitytype_list = FacilityType.objects.filter( - responsible_team__camp__slug=self.camp_slug, - ) - map_data.update({"facilitytype_list": list(facilitytype_list.values())}) - layers = self.get_layers() - map_data.update( - { - "layers": list( - layers.values( - "description", - "name", - "slug", - "uuid", - "icon", - "invisible", - "group__name", - ), - ), - }, - ) - for facility in map_data["facilitytype_list"]: - facility["url"] = reverse( - "facilities:facility_list_geojson", - kwargs={ - "camp_slug": self.camp_slug, - "facility_type_slug": facility["slug"], - }, - ) - for layer in map_data["layers"]: - layer["url"] = reverse( - "maps:map_layer_geojson", - kwargs={"layer_slug": layer["slug"]}, - ) - return map_data - - # ################# LAYERS ######################## @@ -340,7 +269,7 @@ def get_context_data(self, **kwargs): return context -class MapFeatureCreateView(LayerMapperViewMixin, CreateView): +class MapFeatureCreateView(LayerMapperViewMixin, LayerMapMixin, CreateView): model = Feature template_name = "maps_feature_form.html" fields = [ @@ -357,8 +286,7 @@ class MapFeatureCreateView(LayerMapperViewMixin, CreateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - helper = MapLayerHelper(self.kwargs["camp_slug"], self.request) - context.update({"mapData": helper.get_map_data()}) + context.update({"mapData": self.get_map_data()}) return context def get_form(self, *args, **kwargs): @@ -387,7 +315,7 @@ def get_success_url(self): ) -class MapFeatureUpdateView(LayerMapperViewMixin, UpdateView): +class MapFeatureUpdateView(LayerMapperViewMixin, LayerMapMixin, UpdateView): model = Feature slug_url_kwarg = "feature_uuid" slug_field = "uuid" @@ -405,8 +333,7 @@ class MapFeatureUpdateView(LayerMapperViewMixin, UpdateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - helper = MapLayerHelper(self.kwargs["camp_slug"], self.request) - context.update({"mapData": helper.get_map_data()}) + context.update({"mapData": self.get_map_data()}) return context def get_form(self, *args, **kwargs): diff --git a/src/maps/mixins.py b/src/maps/mixins.py index f37ce7014..a36ceab77 100644 --- a/src/maps/mixins.py +++ b/src/maps/mixins.py @@ -5,13 +5,18 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from django.db.models import QuerySet from django.http import HttpRequest from django.contrib import messages from django.core.exceptions import PermissionDenied +from django.db.models import Q from django.shortcuts import get_object_or_404 +from django.templatetags.static import static +from django.urls import reverse from camps.mixins import CampViewMixin +from facilities.models import FacilityType from .models import ExternalLayer from .models import Layer @@ -99,3 +104,69 @@ def setup(self, request: HttpRequest, *args, **kwargs) -> None: return messages.error(request, "No thanks") raise PermissionDenied + + +class LayerMapMixin: + """Mixin for loading the map data from the layers.""" + + camp_slug = None + + def setup(self, request: HttpRequest, *args, **kwargs) -> None: + """Class init method.""" + super().setup(request, *args, **kwargs) + self.camp_slug = kwargs["camp_slug"] + + def get_layers(self) -> QuerySet: + """Method to get the layers the user has access to.""" + user_teams = [] + if not self.request.user.is_anonymous: + user_teams = self.request.user.teammember_set.filter( + team__camp__slug=self.camp_slug, + ).values_list("team__name", flat=True) + return Layer.objects.filter( + ((Q(responsible_team__camp__slug=self.camp_slug) | Q(responsible_team=None)) & Q(public=True)) + | (Q(responsible_team__name__in=user_teams) & Q(public=False)), + ) + + def get_map_data(self) -> dict: + """Method to return the map_data.""" + map_data = { + "grid": static("json/grid.geojson"), + "loggedIn": self.request.user.is_authenticated, + "layers": [], + "facilitytype_list": [], + } + facilitytype_list = FacilityType.objects.filter( + responsible_team__camp__slug=self.camp_slug, + ) + map_data.update({"facilitytype_list": list(facilitytype_list.values())}) + layers = self.get_layers() + map_data.update( + { + "layers": list( + layers.values( + "description", + "name", + "slug", + "uuid", + "icon", + "invisible", + "group__name", + ), + ), + }, + ) + for facility in map_data["facilitytype_list"]: + facility["url"] = reverse( + "facilities:facility_list_geojson", + kwargs={ + "camp_slug": self.camp_slug, + "facility_type_slug": facility["slug"], + }, + ) + for layer in map_data["layers"]: + layer["url"] = reverse( + "maps:map_layer_geojson", + kwargs={"layer_slug": layer["slug"]}, + ) + return map_data