maps: define tiles layers with opacity (#22639)
This commit is contained in:
parent
9881331665
commit
9a08778110
|
@ -73,12 +73,25 @@ class MapLayerForm(forms.ModelForm):
|
|||
class MapLayerOptionsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = MapLayerOptions
|
||||
fields = ['map_layer']
|
||||
fields = ['map_layer', 'opacity']
|
||||
widgets = {
|
||||
'opacity': forms.NumberInput(attrs={'step': 0.1, 'min': 0, 'max': 1})
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.kind = kwargs.pop('kind')
|
||||
super(MapLayerOptionsForm, self).__init__(*args, **kwargs)
|
||||
if self.kind == 'geojson':
|
||||
self.fields['map_layer'].queryset = self.instance.map_cell.get_free_geojson_layers()
|
||||
# if edition, no possibility to change the layer
|
||||
if self.instance.pk:
|
||||
del self.fields['map_layer']
|
||||
else:
|
||||
self.fields['map_layer'].queryset = self.instance.map_cell.get_free_tiles_layers()
|
||||
if self.kind == 'geojson':
|
||||
self.fields['map_layer'].queryset = self.instance.map_cell.get_free_geojson_layers()
|
||||
else:
|
||||
self.fields['map_layer'].queryset = self.instance.map_cell.get_free_tiles_layers()
|
||||
# init opacity field only for tiles layers
|
||||
if self.kind == 'geojson':
|
||||
del self.fields['opacity']
|
||||
else:
|
||||
self.fields['opacity'].required = True
|
||||
self.fields['opacity'].initial = 1
|
||||
|
|
|
@ -94,6 +94,45 @@ class MapCellAddLayer(CreateView):
|
|||
map_cell_add_layer = MapCellAddLayer.as_view()
|
||||
|
||||
|
||||
class MapCellEditLayer(UpdateView):
|
||||
form_class = MapLayerOptionsForm
|
||||
template_name = 'maps/layer_options_form.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
self.cell = CellBase.get_cell(kwargs['cell_reference'], page=kwargs['page_pk'])
|
||||
except Map.DoesNotExist:
|
||||
raise Http404
|
||||
self.object = get_object_or_404(
|
||||
MapLayerOptions,
|
||||
pk=kwargs['layeroptions_pk'],
|
||||
map_cell=self.cell)
|
||||
return super(MapCellEditLayer, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
return self.object
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(MapCellEditLayer, self).get_form_kwargs()
|
||||
kwargs['kind'] = self.object.map_layer.kind
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
PageSnapshot.take(
|
||||
self.cell.page,
|
||||
request=self.request,
|
||||
comment=_('changed options of layer "%s" in cell "%s"') % (form.instance.map_layer, self.cell))
|
||||
return super(MapCellEditLayer, self).form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return '%s#cell-%s' % (
|
||||
reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')}),
|
||||
self.kwargs['cell_reference'])
|
||||
|
||||
|
||||
map_cell_edit_layer = MapCellEditLayer.as_view()
|
||||
|
||||
|
||||
class MapCellDeleteLayer(DeleteView):
|
||||
template_name = 'combo/generic_confirm_delete.html'
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('maps', '0009_map_layer_kind'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='maplayeroptions',
|
||||
name='opacity',
|
||||
field=models.FloatField(
|
||||
help_text='Float value between 0 and 1', null=True,
|
||||
validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)], verbose_name='Opacity'),
|
||||
),
|
||||
]
|
|
@ -17,6 +17,7 @@
|
|||
import json
|
||||
|
||||
from django.core import serializers
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
from django.utils import six
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
@ -333,6 +334,38 @@ class Map(CellBase):
|
|||
def is_enabled(cls):
|
||||
return MapLayer.objects.exists()
|
||||
|
||||
def get_tiles_layers(self):
|
||||
tiles_layers = []
|
||||
options_qs = (
|
||||
self.maplayeroptions_set
|
||||
.filter(map_layer__kind='tiles')
|
||||
.select_related('map_layer')
|
||||
.order_by('-opacity'))
|
||||
for options in options_qs:
|
||||
tiles_layers.append({
|
||||
'tile_urltemplate': options.map_layer.tiles_template_url,
|
||||
'map_attribution': options.map_layer.tiles_attribution,
|
||||
'opacity': options.opacity or 0,
|
||||
})
|
||||
# check if at least one layer with opacity set to 1 exists
|
||||
if any([l['opacity'] == 1 for l in tiles_layers]):
|
||||
return tiles_layers
|
||||
# add the default tiles layer
|
||||
default_tiles_layer = MapLayer.get_default_tiles_layer()
|
||||
if default_tiles_layer is not None:
|
||||
tiles_layers.insert(0, {
|
||||
'tile_urltemplate': default_tiles_layer.tiles_template_url,
|
||||
'map_attribution': default_tiles_layer.tiles_attribution,
|
||||
'opacity': 1,
|
||||
})
|
||||
else:
|
||||
tiles_layers.insert(0, {
|
||||
'tile_urltemplate': settings.COMBO_MAP_TILE_URLTEMPLATE,
|
||||
'map_attribution': settings.COMBO_MAP_ATTRIBUTION,
|
||||
'opacity': 1,
|
||||
})
|
||||
return tiles_layers
|
||||
|
||||
def get_cell_extra_context(self, context):
|
||||
ctx = super(Map, self).get_cell_extra_context(context)
|
||||
ctx['title'] = self.title
|
||||
|
@ -344,13 +377,7 @@ class Map(CellBase):
|
|||
ctx['min_zoom'] = self.min_zoom
|
||||
ctx['max_zoom'] = self.max_zoom
|
||||
ctx['geojson_url'] = reverse_lazy('mapcell-geojson', kwargs={'cell_id': self.pk})
|
||||
default_tiles_layer = MapLayer.get_default_tiles_layer()
|
||||
if default_tiles_layer is not None:
|
||||
ctx['tile_urltemplate'] = default_tiles_layer.tiles_template_url
|
||||
ctx['map_attribution'] = default_tiles_layer.tiles_attribution
|
||||
else:
|
||||
ctx['tile_urltemplate'] = settings.COMBO_MAP_TILE_URLTEMPLATE
|
||||
ctx['map_attribution'] = settings.COMBO_MAP_ATTRIBUTION
|
||||
ctx['tiles_layers'] = self.get_tiles_layers()
|
||||
ctx['max_bounds'] = settings.COMBO_MAP_MAX_BOUNDS
|
||||
ctx['group_markers'] = self.group_markers
|
||||
ctx['marker_behaviour_onclick'] = self.marker_behaviour_onclick
|
||||
|
@ -400,6 +427,14 @@ class Map(CellBase):
|
|||
class MapLayerOptions(models.Model):
|
||||
map_cell = models.ForeignKey(Map, on_delete=models.CASCADE, db_column='map_id')
|
||||
map_layer = models.ForeignKey(MapLayer, verbose_name=_('Layer'), on_delete=models.CASCADE, db_column='maplayer_id')
|
||||
opacity = models.FloatField(
|
||||
verbose_name=_('Opacity'),
|
||||
validators=[
|
||||
validators.MinValueValidator(0),
|
||||
validators.MaxValueValidator(1)],
|
||||
null=True,
|
||||
help_text=_('Float value between 0 and 1'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = 'maps_map_layers'
|
||||
|
|
|
@ -124,8 +124,6 @@ $(function() {
|
|||
map_options.zoomControl = false;
|
||||
var latlng = [$map_widget.data('init-lat'), $map_widget.data('init-lng')];
|
||||
var geojson_url = $map_widget.data('geojson-url');
|
||||
var map_tile_url = $map_widget.data('tile-urltemplate');
|
||||
var map_attribution = $map_widget.data('map-attribution');
|
||||
if ($map_widget.data('max-bounds-lat1')) {
|
||||
map_options.maxBounds = L.latLngBounds(
|
||||
L.latLng($map_widget.data('max-bounds-lat1'), $map_widget.data('max-bounds-lng1')),
|
||||
|
@ -175,11 +173,18 @@ $(function() {
|
|||
});
|
||||
}
|
||||
|
||||
L.tileLayer(map_tile_url,
|
||||
{
|
||||
attribution: map_attribution,
|
||||
maxZoom: map_options.maxZoom
|
||||
}).addTo(map);
|
||||
var map_id = $map_widget.data('cell-id');
|
||||
var tiles_layers = window['tiles_'+map_id];
|
||||
$.each(tiles_layers, function(idx, layer) {
|
||||
L.tileLayer(
|
||||
layer.tile_urltemplate,
|
||||
{
|
||||
attribution: layer.map_attribution,
|
||||
opacity: layer.opacity,
|
||||
maxZoom: map_options.maxZoom
|
||||
}
|
||||
).addTo(map);
|
||||
});
|
||||
if (geojson_url) {
|
||||
map.add_geojson_layer(function(geo_json) {
|
||||
var bounds = geo_json.getBounds();
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
data-init-zoom="{{ initial_zoom }}" data-min-zoom="{{ min_zoom }}"
|
||||
data-max-zoom="{{ max_zoom }}" data-init-lat="{{ init_lat }}"
|
||||
data-init-lng="{{ init_lng }}" data-geojson-url="{{ geojson_url }}"
|
||||
data-tile-urltemplate="{{ tile_urltemplate}}" data-map-attribution="{{ map_attribution}}"
|
||||
data-include-geoloc-button="true"
|
||||
{% if group_markers %}data-group-markers="1"{% endif %}
|
||||
data-marker-behaviour-onclick="{{ cell.marker_behaviour_onclick }}"
|
||||
|
@ -16,7 +15,18 @@
|
|||
data-max-bounds-lat2="{{ max_bounds.corner2.lat }}"
|
||||
data-max-bounds-lng2="{{ max_bounds.corner2.lng }}"
|
||||
{% endif %}
|
||||
data-cell-id="{{ cell.pk }}"
|
||||
>
|
||||
{% endlocalize %}
|
||||
<script>
|
||||
var tiles_{{ cell.pk }} = [];
|
||||
{% for layer in tiles_layers %}
|
||||
tiles_{{ cell.pk }}.push({
|
||||
tile_urltemplate: {{ layer.tile_urltemplate|as_json|safe }},
|
||||
map_attribution: {{ layer.map_attribution|as_json|safe }},
|
||||
opacity: {{ layer.opacity|as_json|safe }}
|
||||
});
|
||||
{% endfor %}
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
{% for option in options %}
|
||||
<li>
|
||||
<span>{{ option.map_layer.label }} {% if option.map_layer.kind == 'tiles' %}({{ option.map_layer.get_kind_display }}){% endif %}</span>
|
||||
{% if option.map_layer.kind == 'tiles' %}
|
||||
<a rel="popup" title="{% trans "Edit" %}" class="link-action-icon edit" href="{% url 'maps-manager-cell-edit-layer' page_pk=page.pk cell_reference=cell.get_reference layeroptions_pk=option.pk %}">{% trans "Edit" %}</a>
|
||||
{% endif %}
|
||||
<a rel="popup" title="{% trans "Delete" %}" class="link-action-icon delete" href="{% url 'maps-manager-cell-delete-layer' page_pk=page.pk cell_reference=cell.get_reference layeroptions_pk=option.pk %}">{% trans "Delete" %}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
|
|
@ -32,6 +32,9 @@ maps_manager_urls = [
|
|||
url(r'^pages/(?P<page_pk>\d+)/cell/(?P<cell_reference>[\w_-]+)/add-layer/(?P<kind>geojson|tiles)/$',
|
||||
manager_views.map_cell_add_layer,
|
||||
name='maps-manager-cell-add-layer'),
|
||||
url(r'^pages/(?P<page_pk>\d+)/cell/(?P<cell_reference>[\w_-]+)/layer/(?P<layeroptions_pk>\d+)/edit/$',
|
||||
manager_views.map_cell_edit_layer,
|
||||
name='maps-manager-cell-edit-layer'),
|
||||
url(r'^pages/(?P<page_pk>\d+)/cell/(?P<cell_reference>[\w_-]+)/layer/(?P<layeroptions_pk>\d+)/delete/$',
|
||||
manager_views.map_cell_delete_layer,
|
||||
name='maps-manager-cell-delete-layer'),
|
||||
|
|
|
@ -137,7 +137,7 @@ def test_cell_rendering(app, layer, tiles_layer):
|
|||
page.save()
|
||||
cell = Map(page=page, placeholder='content', order=0, title='Map with points')
|
||||
cell.save()
|
||||
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
|
||||
options = MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
|
||||
context = {'request': RequestFactory().get('/')}
|
||||
rendered = cell.render(context)
|
||||
assert 'data-init-zoom="13"' in rendered
|
||||
|
@ -147,8 +147,6 @@ def test_cell_rendering(app, layer, tiles_layer):
|
|||
assert 'data-init-lng="2.3233688436448574"' in rendered
|
||||
assert 'data-geojson-url="/ajax/mapcell/geojson/1/"' in rendered
|
||||
assert 'data-group-markers="1"' not in rendered
|
||||
assert 'data-tile-urltemplate="%s"' % tiles_layer.tiles_template_url in rendered
|
||||
assert 'data-map-attribution="%s"' % tiles_layer.tiles_attribution in rendered
|
||||
resp = app.get('/test_map_cell/')
|
||||
assert 'xstatic/leaflet.js' in resp.text
|
||||
assert 'js/combo.map.js' in resp.text
|
||||
|
@ -160,16 +158,60 @@ def test_cell_rendering(app, layer, tiles_layer):
|
|||
rendered = cell.render(context)
|
||||
assert 'data-group-markers="1"' in rendered
|
||||
|
||||
|
||||
def test_cell_tiles_layers(tiles_layer):
|
||||
page = Page.objects.create(title='xxx', slug='test_map_cell', template_name='standard')
|
||||
cell = Map.objects.create(page=page, placeholder='content', order=0, title='Map with points')
|
||||
|
||||
# no tiles layer for this map, take default tiles layers, tiles_layer
|
||||
assert cell.get_tiles_layers() == [{
|
||||
'tile_urltemplate': tiles_layer.tiles_template_url,
|
||||
'map_attribution': tiles_layer.tiles_attribution,
|
||||
'opacity': 1,
|
||||
}]
|
||||
|
||||
# tiles_layer is not set as default, fallback on settings
|
||||
tiles_layer.tiles_default = False
|
||||
tiles_layer.save()
|
||||
rendered = cell.render(context)
|
||||
assert 'data-tile-urltemplate="%s"' % settings.COMBO_MAP_TILE_URLTEMPLATE in rendered
|
||||
assert 'data-map-attribution="%s"' % escape(settings.COMBO_MAP_ATTRIBUTION) in rendered
|
||||
assert cell.get_tiles_layers() == [{
|
||||
'tile_urltemplate': settings.COMBO_MAP_TILE_URLTEMPLATE,
|
||||
'map_attribution': settings.COMBO_MAP_ATTRIBUTION,
|
||||
'opacity': 1,
|
||||
}]
|
||||
|
||||
tiles_layer.delete()
|
||||
rendered = cell.render(context)
|
||||
assert 'data-tile-urltemplate="%s"' % settings.COMBO_MAP_TILE_URLTEMPLATE in rendered
|
||||
assert 'data-map-attribution="%s"' % escape(settings.COMBO_MAP_ATTRIBUTION) in rendered
|
||||
# add a tile layer to the map, with opacity 1
|
||||
options = MapLayerOptions.objects.create(map_cell=cell, map_layer=tiles_layer, opacity=1)
|
||||
assert cell.get_tiles_layers() == [{
|
||||
'tile_urltemplate': tiles_layer.tiles_template_url,
|
||||
'map_attribution': tiles_layer.tiles_attribution,
|
||||
'opacity': 1,
|
||||
}]
|
||||
|
||||
# opacity is less than 1 => add default tiles layer, defined in settings
|
||||
options.opacity = 0.5
|
||||
options.save()
|
||||
assert cell.get_tiles_layers() == [{
|
||||
'tile_urltemplate': settings.COMBO_MAP_TILE_URLTEMPLATE,
|
||||
'map_attribution': settings.COMBO_MAP_ATTRIBUTION,
|
||||
'opacity': 1,
|
||||
}, {
|
||||
'tile_urltemplate': tiles_layer.tiles_template_url,
|
||||
'map_attribution': tiles_layer.tiles_attribution,
|
||||
'opacity': 0.5,
|
||||
}]
|
||||
|
||||
# set tiles_layer as default => add tiles_layer
|
||||
tiles_layer.tiles_default = True
|
||||
tiles_layer.save()
|
||||
assert cell.get_tiles_layers() == [{
|
||||
'tile_urltemplate': tiles_layer.tiles_template_url,
|
||||
'map_attribution': tiles_layer.tiles_attribution,
|
||||
'opacity': 1,
|
||||
}, {
|
||||
'tile_urltemplate': tiles_layer.tiles_template_url,
|
||||
'map_attribution': tiles_layer.tiles_attribution,
|
||||
'opacity': 0.5,
|
||||
}]
|
||||
|
||||
|
||||
def test_get_geojson_on_non_public_page(app, layer):
|
||||
|
|
|
@ -219,6 +219,7 @@ def test_add_delete_layer(app, admin_user, layer, tiles_layer):
|
|||
assert list(cell.get_free_tiles_layers()) == [tiles_layer]
|
||||
resp = resp.click(href='.*/add-layer/geojson/$')
|
||||
assert list(resp.context['form'].fields['map_layer'].queryset) == [layer]
|
||||
assert 'opacity' not in resp.context['form'].fields
|
||||
resp.forms[0]['map_layer'] = layer.pk
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.status_int == 302
|
||||
|
@ -229,6 +230,7 @@ def test_add_delete_layer(app, admin_user, layer, tiles_layer):
|
|||
assert options.map_layer == layer
|
||||
|
||||
resp = resp.follow()
|
||||
assert '/layer/%s/edit/' % options.pk not in resp.text
|
||||
assert list(cell.get_free_geojson_layers()) == []
|
||||
assert list(cell.get_free_tiles_layers()) == [tiles_layer]
|
||||
assert '/add-layer/geojson/' not in resp.text
|
||||
|
@ -244,6 +246,7 @@ def test_add_delete_layer(app, admin_user, layer, tiles_layer):
|
|||
resp = resp.click(href='.*/add-layer/tiles/$')
|
||||
assert list(resp.context['form'].fields['map_layer'].queryset) == [tiles_layer]
|
||||
resp.forms[0]['map_layer'] = tiles_layer.pk
|
||||
resp.forms[0]['opacity'] = 1
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.status_int == 302
|
||||
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
|
||||
|
@ -251,6 +254,17 @@ def test_add_delete_layer(app, admin_user, layer, tiles_layer):
|
|||
options = MapLayerOptions.objects.get()
|
||||
assert options.map_cell == cell
|
||||
assert options.map_layer == tiles_layer
|
||||
assert options.opacity == 1
|
||||
|
||||
resp = resp.follow()
|
||||
resp = resp.click(href='.*/layer/%s/edit/$' % options.pk)
|
||||
assert 'map_layer' not in resp.context['form'].fields
|
||||
resp.forms[0]['opacity'] = 0.5
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.status_int == 302
|
||||
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
|
||||
options.refresh_from_db()
|
||||
assert options.opacity == 0.5
|
||||
|
||||
resp = resp.follow()
|
||||
assert list(cell.get_free_geojson_layers()) == [layer]
|
||||
|
|
Loading…
Reference in New Issue