From a89ba57ef89bed22f38938406aa0b73964d78739 Mon Sep 17 00:00:00 2001 From: Josue Kouka Date: Thu, 15 Feb 2018 11:21:37 +0100 Subject: [PATCH] maps: add handling of marker behaviour on click (#21034) --- .../maps/migrations/0006_auto_20180215_1020.py | 19 ++++++++++++ combo/apps/maps/models.py | 36 +++++++++++++++++++++- combo/apps/maps/static/js/combo.map.js | 18 +++++++++++ combo/apps/maps/templates/maps/map_cell.html | 1 + tests/test_maps_cells.py | 23 +++++++++++++- 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 combo/apps/maps/migrations/0006_auto_20180215_1020.py diff --git a/combo/apps/maps/migrations/0006_auto_20180215_1020.py b/combo/apps/maps/migrations/0006_auto_20180215_1020.py new file mode 100644 index 0000000..1a5ae46 --- /dev/null +++ b/combo/apps/maps/migrations/0006_auto_20180215_1020.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('maps', '0005_auto_20180212_1742'), + ] + + operations = [ + migrations.AddField( + model_name='map', + name='marker_onclick_behaviour', + field=models.CharField(default=b'none', max_length=32, verbose_name='Marker behaviour on clik', choices=[(b'none', 'Nothing'), (b'display_data', 'Display data in popup')]), + ), + ] diff --git a/combo/apps/maps/models.py b/combo/apps/maps/models.py index 85f0837..b3b8ac6 100644 --- a/combo/apps/maps/models.py +++ b/combo/apps/maps/models.py @@ -18,6 +18,7 @@ import json from django.core import serializers from django.db import models +from django.utils.html import escape from django.utils.text import slugify from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse_lazy @@ -67,6 +68,11 @@ ICONS = [ ('wheelchair', _('Wheelchair')), ] +MARKER_ONCLIK_BEHAVIOUR = [ + ('none', _('Nothing')), + ('display_data', _('Display data in popup')), +] + ZOOM_LEVELS = [ ('0', _('Whole world')), ('9', _('Wide area')), ('11', _('Area')), @@ -76,6 +82,24 @@ ZOOM_LEVELS = [ ('0', _('Whole world')), ('19', _('Ant')),] +def escape_properties(properties): + if isinstance(properties, (list,)): + escaped = [] + for prop in properties: + if isinstance(prop, (list,)): + prop = escape_properties(prop) + escaped.append(prop) + else: + escaped.append(escape(prop)) + else: + escaped = {} + for key, value in properties.iteritems(): + if isinstance(value, (dict,)): + value = escape_properties(value) + escaped[escape(key)] = escape(value) + return escaped + + class MapLayerManager(models.Manager): def get_by_natural_key(self, slug): return self.get(slug=slug) @@ -172,6 +196,13 @@ class MapLayer(models.Model): features = [x for x in features if match(x)] for feature in features: + properties = feature.get('properties', {}) + display_fields = properties.get('display_fields') + if display_fields: + feature['properties']['display_fields'] = escape_properties(display_fields) + else: + feature['properties'] = escape_properties(properties) + feature['properties']['layer'] = { 'colour': self.marker_colour, 'icon_colour': self.icon_colour, @@ -201,6 +232,8 @@ class Map(CellBase): max_zoom = models.CharField(_('Maximal zoom level'), max_length=2, choices=ZOOM_LEVELS, default=19) group_markers = models.BooleanField(_('Group markers in clusters'), default=False) + marker_onclick_behaviour = models.CharField(_('Marker behaviour on clik'), max_length=32, + default='none', choices=MARKER_ONCLIK_BEHAVIOUR) layers = models.ManyToManyField(MapLayer, verbose_name=_('Layers'), blank=True) template_name = 'maps/map_cell.html' @@ -218,7 +251,7 @@ class Map(CellBase): def get_default_form_class(self): fields = ('title', 'initial_state', 'initial_zoom', 'min_zoom', - 'max_zoom', 'group_markers', 'layers') + 'max_zoom', 'group_markers', 'marker_onclick_behaviour', 'layers') widgets = {'layers': forms.widgets.CheckboxSelectMultiple} return forms.models.modelform_factory(self.__class__, fields=fields, widgets=widgets) @@ -248,4 +281,5 @@ class Map(CellBase): ctx['map_attribution'] = settings.COMBO_MAP_ATTRIBUTION ctx['max_bounds'] = settings.COMBO_MAP_MAX_BOUNDS ctx['group_markers'] = self.group_markers + ctx['marker_onclick_behaviour'] = self.marker_onclick_behaviour return ctx diff --git a/combo/apps/maps/static/js/combo.map.js b/combo/apps/maps/static/js/combo.map.js index 0279a44..7cb6b35 100644 --- a/combo/apps/maps/static/js/combo.map.js +++ b/combo/apps/maps/static/js/combo.map.js @@ -16,6 +16,24 @@ $(function() { var geo_json = L.geoJson(data, { onEachFeature: function(feature, layer) { $(cell).trigger('combo:map-feature-prepare', {'feature': feature, 'layer': layer}); + var marker_onclick_behaviour = $map_widget.data('marker-onclick-behaviour'); + if (marker_onclick_behaviour === 'display_data') { + var popup = ''; + if (feature.properties.display_fields) { + $.each(feature.properties.display_fields, function(key, value) { + popup += ''; + } + }); + } + layer.bindPopup(popup); + } }, pointToLayer: function (feature, latlng) { var markerStyles = "background-color: " + feature.properties.layer.colour + ";"; diff --git a/combo/apps/maps/templates/maps/map_cell.html b/combo/apps/maps/templates/maps/map_cell.html index a37a2e9..06fb6a8 100644 --- a/combo/apps/maps/templates/maps/map_cell.html +++ b/combo/apps/maps/templates/maps/map_cell.html @@ -7,6 +7,7 @@ data-init-lng="{{ init_lng }}" data-geojson-url="{{ geojson_url }}" data-tile-urltemplate="{{ tile_urltemplate}}" data-map-attribution="{{ map_attribution}}" {% if group_markers %}data-group-markers="1"{% endif %} + data-marker-onclick-behaviour="{{ cell.marker_onclick_behaviour }}" {% if max_bounds.corner1.lat %} data-max-bounds-lat1="{{ max_bounds.corner1.lat }}" data-max-bounds-lng1="{{ max_bounds.corner1.lng }}" diff --git a/tests/test_maps_cells.py b/tests/test_maps_cells.py index c50fc81..6f239b0 100644 --- a/tests/test_maps_cells.py +++ b/tests/test_maps_cells.py @@ -8,9 +8,10 @@ from django.test.client import RequestFactory from django.test import Client from django.core.urlresolvers import reverse from django.contrib.auth.models import Group +from django.utils.html import escape from combo.data.models import Page -from combo.apps.maps.models import MapLayer, Map +from combo.apps.maps.models import MapLayer, Map, escape_properties pytestmark = pytest.mark.django_db @@ -225,3 +226,23 @@ def test_get_geojson(layer, user): assert len(json.loads(resp.content)['features']) == 1 assert 'orig=combo' in requests_get.call_args[0][1] assert not 'email=admin%40localhost&' in requests_get.call_args[0][1] + + +def test_feature_properties_escaping(): + list_properties = [ + ['code', ''], + ['category', 'potholes'], + ] + escaped_list_properties = escape_properties(list_properties) + assert escaped_list_properties[0][0] == 'code' + assert escaped_list_properties[0][1] == escape(list_properties[0][1]) + assert escaped_list_properties[1][0] == 'category' + assert escaped_list_properties[1][1] == 'potholes' + dict_properties = { + 'code': '', + 'category': 'potholes' + } + escaped_dict_properties = escape_properties(dict_properties) + for key, value in escaped_dict_properties.items(): + assert key in ('code', 'category') + assert value in (escape(dict_properties['code']), 'potholes') -- 2.14.2