0001-maps-move-properties-from-layer-to-cell-57760.patch
combo/apps/maps/forms.py | ||
---|---|---|
66 | 66 |
'icon_colour', |
67 | 67 |
'cache_duration', |
68 | 68 |
'include_user_identifier', |
69 |
'properties', |
|
70 | 69 |
'geojson_query_parameter', |
71 | 70 |
'geojson_accepts_circle_param', |
72 | 71 |
] |
... | ... | |
87 | 86 |
class MapLayerOptionsForm(forms.ModelForm): |
88 | 87 |
class Meta: |
89 | 88 |
model = MapLayerOptions |
90 |
fields = ['map_layer', 'opacity'] |
|
89 |
fields = ['map_layer', 'opacity', 'properties']
|
|
91 | 90 |
widgets = {'opacity': forms.NumberInput(attrs={'step': 0.1, 'min': 0, 'max': 1})} |
92 | 91 | |
93 | 92 |
def __init__(self, *args, **kwargs): |
... | ... | |
107 | 106 |
else: |
108 | 107 |
self.fields['opacity'].required = True |
109 | 108 |
self.fields['opacity'].initial = 1 |
109 |
del self.fields['properties'] |
combo/apps/maps/migrations/0017_auto_20211104_1559.py | ||
---|---|---|
1 |
# Generated by Django 2.2.19 on 2021-11-04 14:59 |
|
2 | ||
3 |
from django.db import migrations, models |
|
4 | ||
5 | ||
6 |
class Migration(migrations.Migration): |
|
7 | ||
8 |
dependencies = [ |
|
9 |
('maps', '0016_auto_20210927_1945'), |
|
10 |
] |
|
11 | ||
12 |
operations = [ |
|
13 |
migrations.AddField( |
|
14 |
model_name='maplayeroptions', |
|
15 |
name='properties', |
|
16 |
field=models.CharField( |
|
17 |
blank=True, |
|
18 |
help_text='List of properties to include, separated by commas', |
|
19 |
max_length=500, |
|
20 |
verbose_name='Properties', |
|
21 |
), |
|
22 |
), |
|
23 |
] |
combo/apps/maps/migrations/0018_auto_20211104_1559.py | ||
---|---|---|
1 |
# Generated by Django 2.2.19 on 2021-11-04 14:59 |
|
2 | ||
3 |
from django.db import migrations |
|
4 | ||
5 | ||
6 |
def populate_cell_properties(apps, schema_editor): |
|
7 |
Map = apps.get_model('maps', 'Map') |
|
8 |
MapLayerOptions = apps.get_model('maps', 'MapLayerOptions') |
|
9 | ||
10 |
for cell in Map.objects.all(): |
|
11 |
for layer in cell.layers.all(): |
|
12 |
MapLayerOptions.objects.update_or_create( |
|
13 |
map_cell=cell, map_layer=layer, defaults={'properties': layer.properties} |
|
14 |
) |
|
15 | ||
16 | ||
17 |
class Migration(migrations.Migration): |
|
18 | ||
19 |
dependencies = [ |
|
20 |
('maps', '0017_auto_20211104_1559'), |
|
21 |
] |
|
22 | ||
23 |
operations = [ |
|
24 |
migrations.RunPython(populate_cell_properties, migrations.RunPython.noop), |
|
25 |
] |
combo/apps/maps/migrations/0019_auto_20211104_1603.py | ||
---|---|---|
1 |
# Generated by Django 2.2.19 on 2021-11-04 15:03 |
|
2 | ||
3 |
from django.db import migrations |
|
4 | ||
5 | ||
6 |
class Migration(migrations.Migration): |
|
7 | ||
8 |
dependencies = [ |
|
9 |
('maps', '0018_auto_20211104_1559'), |
|
10 |
] |
|
11 | ||
12 |
operations = [ |
|
13 |
migrations.RemoveField( |
|
14 |
model_name='maplayer', |
|
15 |
name='properties', |
|
16 |
), |
|
17 |
] |
combo/apps/maps/models.py | ||
---|---|---|
21 | 21 |
from django.conf import settings |
22 | 22 |
from django.core import serializers, validators |
23 | 23 |
from django.db import models |
24 |
from django.db.models import OuterRef, Subquery |
|
24 | 25 |
from django.urls import reverse |
25 | 26 |
from django.utils.encoding import python_2_unicode_compatible |
26 | 27 |
from django.utils.html import escape |
... | ... | |
129 | 130 |
icon_colour = models.CharField(_('Icon colour'), max_length=7, default='#000000') |
130 | 131 |
cache_duration = models.PositiveIntegerField(_('Cache duration'), default=60, help_text=_('In seconds.')) |
131 | 132 |
include_user_identifier = models.BooleanField(_('Include user identifier in request'), default=True) |
132 |
properties = models.CharField( |
|
133 |
_('Properties'), |
|
134 |
max_length=500, |
|
135 |
blank=True, |
|
136 |
help_text=_('List of properties to include, separated by commas'), |
|
137 |
) |
|
138 | 133 |
geojson_query_parameter = models.CharField( |
139 | 134 |
_("Query parameter for fulltext requests"), |
140 | 135 |
max_length=100, |
... | ... | |
195 | 190 |
layer = next(serializers.deserialize('json', json.dumps([json_layer]), ignorenonexistent=True)) |
196 | 191 |
layer.save() |
197 | 192 | |
198 |
def get_geojson(self, request): |
|
193 |
def get_geojson(self, request, properties=''):
|
|
199 | 194 |
geojson_url = get_templated_url(self.geojson_url) |
200 | 195 | |
201 | 196 |
query_parameter = self.geojson_query_parameter |
... | ... | |
252 | 247 |
else: |
253 | 248 |
features = data |
254 | 249 | |
255 |
properties = [] |
|
256 |
if self.properties: |
|
257 |
properties = [x.strip() for x in self.properties.split(',')] |
|
250 |
properties = [x.strip() for x in properties.split(',') if x.strip()] |
|
251 |
if properties: |
|
258 | 252 |
for feature in features: |
259 | 253 |
if 'display_fields' in feature['properties']: |
260 | 254 |
# w.c.s. content, filter fields on varnames |
... | ... | |
457 | 451 |
def get_geojson_layers(self): |
458 | 452 |
if not self.pk: |
459 | 453 |
return [] |
454 |
options = MapLayerOptions.objects.filter(map_cell=self, map_layer=OuterRef('pk')) |
|
455 |
layers = self.layers.filter(kind='geojson').annotate( |
|
456 |
properties=Subquery(options.values('properties')) |
|
457 |
) |
|
460 | 458 |
return [ |
461 | 459 |
{ |
462 | 460 |
'url': reverse('mapcell-geojson', kwargs={'cell_id': self.pk, 'layer_slug': l.slug}), |
... | ... | |
466 | 464 |
'marker_colour': l.marker_colour, |
467 | 465 |
'properties': [x.strip() for x in l.properties.split(',')], |
468 | 466 |
} |
469 |
for l in self.layers.filter(kind='geojson')
|
|
467 |
for l in layers
|
|
470 | 468 |
] |
471 | 469 | |
472 | 470 |
def get_cell_extra_context(self, context): |
... | ... | |
537 | 535 |
null=True, |
538 | 536 |
help_text=_('Float value between 0 (transparent) and 1 (opaque)'), |
539 | 537 |
) |
538 |
properties = models.CharField( |
|
539 |
_('Properties'), |
|
540 |
max_length=500, |
|
541 |
blank=True, |
|
542 |
help_text=_('List of properties to include, separated by commas'), |
|
543 |
) |
|
540 | 544 | |
541 | 545 |
class Meta: |
542 | 546 |
db_table = 'maps_map_layers' |
combo/apps/maps/templates/maps/map_cell_form.html | ||
---|---|---|
11 | 11 |
{% for option in options %} |
12 | 12 |
<li> |
13 | 13 |
<span>{{ option.map_layer.label }} {% if option.map_layer.kind == 'tiles' %}({{ option.map_layer.get_kind_display }}){% endif %}</span> |
14 |
{% if option.map_layer.kind == 'tiles' %} |
|
15 | 14 |
<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> |
16 |
{% endif %} |
|
17 | 15 |
<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> |
18 | 16 |
</li> |
19 | 17 |
{% endfor %} |
combo/apps/maps/views.py | ||
---|---|---|
29 | 29 |
layer = get_object_or_404(cell.layers.all(), kind='geojson', slug=kwargs['layer_slug']) |
30 | 30 |
if not cell.page.is_visible(request.user) or not cell.is_visible(user=request.user): |
31 | 31 |
return HttpResponseForbidden() |
32 |
geojson = layer.get_geojson(request) |
|
32 |
options = cell.maplayeroptions_set.get(map_layer=layer) |
|
33 |
geojson = layer.get_geojson(request, options.properties) |
|
33 | 34 |
content_type = 'application/json' |
34 | 35 |
return HttpResponse(json.dumps(geojson), content_type=content_type) |
tests/test_maps_cells.py | ||
---|---|---|
581 | 581 |
cell.title = 'Map' |
582 | 582 |
cell.save() |
583 | 583 |
layer.save() |
584 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer) |
|
584 |
options = MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
|
|
585 | 585 | |
586 | 586 |
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get: |
587 | 587 |
layer.geojson_url = 'http://example.org/geojson?t1' |
... | ... | |
596 | 596 | |
597 | 597 |
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get: |
598 | 598 |
layer.geojson_url = 'http://example.org/geojson?t2' |
599 |
layer.properties = 'name, hop' |
|
600 | 599 |
layer.save() |
600 |
options.properties = 'name, hop' |
|
601 |
options.save() |
|
601 | 602 |
requests_get.return_value = mock.Mock( |
602 | 603 |
content=SAMPLE_GEOJSON_CONTENT, json=lambda: json.loads(SAMPLE_GEOJSON_CONTENT), status_code=200 |
603 | 604 |
) |
... | ... | |
608 | 609 | |
609 | 610 |
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get: |
610 | 611 |
layer.geojson_url = 'http://example.org/geojson?t3' |
611 |
layer.properties = '' |
|
612 | 612 |
layer.save() |
613 |
options.properties = '' |
|
614 |
options.save() |
|
613 | 615 |
requests_get.return_value = mock.Mock( |
614 | 616 |
content=SAMPLE_WCS_GEOJSON_CONTENT, |
615 | 617 |
json=lambda: json.loads(SAMPLE_WCS_GEOJSON_CONTENT), |
... | ... | |
621 | 623 | |
622 | 624 |
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get: |
623 | 625 |
layer.geojson_url = 'http://example.org/geojson?t4' |
624 |
layer.properties = 'id' |
|
625 | 626 |
layer.save() |
627 |
options.properties = 'id' |
|
628 |
options.save() |
|
626 | 629 |
requests_get.return_value = mock.Mock( |
627 | 630 |
content=SAMPLE_WCS_GEOJSON_CONTENT, |
628 | 631 |
json=lambda: json.loads(SAMPLE_WCS_GEOJSON_CONTENT), |
tests/test_maps_manager.py | ||
---|---|---|
88 | 88 |
assert 'marker_colour' not in resp.context['form'].fields |
89 | 89 |
assert 'icon' not in resp.context['form'].fields |
90 | 90 |
assert 'icon_colour' not in resp.context['form'].fields |
91 |
assert 'properties' not in resp.context['form'].fields |
|
92 | 91 |
assert 'geojson_query_parameter' not in resp.context['form'].fields |
93 | 92 |
assert 'geojson_accepts_circle_param' not in resp.context['form'].fields |
94 | 93 |
resp.forms[0]['label'] = 'Test' |
... | ... | |
150 | 149 |
assert 'marker_colour' not in resp.context['form'] |
151 | 150 |
assert 'icon' not in resp.context['form'] |
152 | 151 |
assert 'icon_colour' not in resp.context['form'] |
153 |
assert 'properties' not in resp.context['form'] |
|
154 | 152 |
resp.forms[0]['tiles_default'] = False |
155 | 153 |
resp = resp.forms[0].submit() |
156 | 154 |
assert resp.location.endswith('/manage/maps/') |
... | ... | |
238 | 236 |
assert options.map_layer == layer |
239 | 237 | |
240 | 238 |
resp = resp.follow() |
241 |
assert '/layer/%s/edit/' % options.pk not in resp.text |
|
242 | 239 |
assert list(cell.get_free_geojson_layers()) == [] |
243 | 240 |
assert list(cell.get_free_tiles_layers()) == [tiles_layer] |
244 | 241 |
assert '/add-layer/geojson/' not in resp.text |
242 | ||
243 |
resp = resp.click(href='.*/layer/%s/edit/$' % options.pk) |
|
244 |
assert 'map_layer' not in resp.context['form'].fields |
|
245 |
assert 'opacity' not in resp.context['form'].fields |
|
246 |
resp.form['properties'] = 'a, b' |
|
247 |
resp = resp.form.submit() |
|
248 |
assert resp.status_int == 302 |
|
249 |
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference())) |
|
250 |
options = MapLayerOptions.objects.get() |
|
251 |
options.refresh_from_db() |
|
252 |
assert options.properties == 'a, b' |
|
253 | ||
254 |
resp = resp.follow() |
|
245 | 255 |
resp = resp.click(href='.*/layer/%s/delete/$' % options.pk) |
246 | 256 |
resp = resp.forms[0].submit() |
247 | 257 |
assert resp.status_int == 302 |
... | ... | |
253 | 263 |
assert list(cell.get_free_tiles_layers()) == [tiles_layer] |
254 | 264 |
resp = resp.click(href='.*/add-layer/tiles/$') |
255 | 265 |
assert list(resp.context['form'].fields['map_layer'].queryset) == [tiles_layer] |
266 |
assert 'properties' not in resp.context['form'].fields |
|
256 | 267 |
resp.forms[0]['map_layer'] = tiles_layer.pk |
257 | 268 |
resp.forms[0]['opacity'] = 1 |
258 | 269 |
resp = resp.forms[0].submit() |
... | ... | |
267 | 278 |
resp = resp.follow() |
268 | 279 |
resp = resp.click(href='.*/layer/%s/edit/$' % options.pk) |
269 | 280 |
assert 'map_layer' not in resp.context['form'].fields |
281 |
assert 'properties' not in resp.context['form'].fields |
|
270 | 282 |
resp.forms[0]['opacity'] = 0.5 |
271 | 283 |
resp = resp.forms[0].submit() |
272 | 284 |
assert resp.status_int == 302 |
273 |
- |