0002-maps-new-MapLayerOptions-model-22639.patch
combo/apps/maps/forms.py | ||
---|---|---|
20 | 20 |
from django.utils.translation import ugettext_lazy as _ |
21 | 21 | |
22 | 22 |
from combo.data.fields import TemplatableURLField |
23 |
from .models import MapLayer |
|
23 |
from .models import MapLayer, MapLayerOptions
|
|
24 | 24 | |
25 | 25 | |
26 | 26 |
class IconRadioSelect(forms.RadioSelect): |
... | ... | |
37 | 37 |
'icon_colour': forms.TextInput(attrs={'type': 'color'}), |
38 | 38 |
} |
39 | 39 | |
40 | ||
41 | 40 |
def __init__(self, *args, **kwargs): |
42 | 41 |
super(MapNewLayerForm, self).__init__(*args, **kwargs) |
43 | 42 |
self.fields['icon'].choices = list( |
... | ... | |
59 | 58 |
super(MapLayerForm, self).__init__(*args, **kwargs) |
60 | 59 |
self.fields['icon'].choices = list( |
61 | 60 |
sorted(self.fields['icon'].choices, key=lambda x: slugify(force_text(x[1])))) |
61 | ||
62 | ||
63 |
class MapLayerOptionsForm(forms.ModelForm): |
|
64 |
class Meta: |
|
65 |
model = MapLayerOptions |
|
66 |
fields = ['map_layer'] |
|
67 | ||
68 |
def __init__(self, *args, **kwargs): |
|
69 |
super(MapLayerOptionsForm, self).__init__(*args, **kwargs) |
|
70 |
self.fields['map_layer'].queryset = self.instance.map_cell.get_free_layers() |
combo/apps/maps/manager_views.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from django.core.urlresolvers import reverse_lazy |
|
18 |
from django.views.generic import (TemplateView, ListView, CreateView, |
|
19 |
UpdateView, DeleteView) |
|
17 |
from django.core.urlresolvers import reverse, reverse_lazy |
|
18 |
from django.http import Http404 |
|
19 |
from django.shortcuts import get_object_or_404 |
|
20 |
from django.utils.translation import ugettext_lazy as _ |
|
21 |
from django.views.generic import ListView, CreateView, UpdateView, DeleteView |
|
20 | 22 | |
23 |
from combo.data.models import CellBase, PageSnapshot |
|
21 | 24 |
from .models import Map |
22 | 25 |
from .models import MapLayer |
23 |
from .forms import MapNewLayerForm, MapLayerForm |
|
26 |
from .models import MapLayerOptions |
|
27 |
from .forms import MapNewLayerForm, MapLayerForm, MapLayerOptionsForm |
|
24 | 28 | |
25 | 29 | |
26 | 30 |
class MapLayerMixin(object): |
... | ... | |
49 | 53 | |
50 | 54 |
class LayerDeleteView(MapLayerMixin, DeleteView): |
51 | 55 |
template_name = 'maps/map_layer_confirm_delete.html' |
56 | ||
57 | ||
58 |
class MapCellAddLayer(CreateView): |
|
59 |
form_class = MapLayerOptionsForm |
|
60 |
template_name = 'maps/layer_options_form.html' |
|
61 | ||
62 |
def dispatch(self, request, *args, **kwargs): |
|
63 |
try: |
|
64 |
self.cell = CellBase.get_cell(kwargs['cell_reference'], page=kwargs['page_pk']) |
|
65 |
except Map.DoesNotExist: |
|
66 |
raise Http404 |
|
67 |
return super(MapCellAddLayer, self).dispatch(request, *args, **kwargs) |
|
68 | ||
69 |
def get_form_kwargs(self): |
|
70 |
kwargs = super(MapCellAddLayer, self).get_form_kwargs() |
|
71 |
kwargs['instance'] = MapLayerOptions(map_cell=self.cell) |
|
72 |
return kwargs |
|
73 | ||
74 |
def form_valid(self, form): |
|
75 |
PageSnapshot.take( |
|
76 |
self.cell.page, |
|
77 |
request=self.request, |
|
78 |
comment=_('added layer "%s" to cell "%s"') % (form.instance.map_layer, self.cell)) |
|
79 |
return super(MapCellAddLayer, self).form_valid(form) |
|
80 | ||
81 |
def get_success_url(self): |
|
82 |
return '%s#cell-%s' % ( |
|
83 |
reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')}), |
|
84 |
self.kwargs['cell_reference']) |
|
85 | ||
86 | ||
87 |
map_cell_add_layer = MapCellAddLayer.as_view() |
|
88 | ||
89 | ||
90 |
class MapCellDeleteLayer(DeleteView): |
|
91 |
template_name = 'combo/generic_confirm_delete.html' |
|
92 | ||
93 |
def dispatch(self, request, *args, **kwargs): |
|
94 |
try: |
|
95 |
self.cell = CellBase.get_cell(kwargs['cell_reference'], page=kwargs['page_pk']) |
|
96 |
except Map.DoesNotExist: |
|
97 |
raise Http404 |
|
98 |
self.object = get_object_or_404( |
|
99 |
MapLayerOptions, |
|
100 |
pk=kwargs['layeroptions_pk'], |
|
101 |
map_cell=self.cell) |
|
102 |
return super(MapCellDeleteLayer, self).dispatch(request, *args, **kwargs) |
|
103 | ||
104 |
def get_object(self, *args, **kwargs): |
|
105 |
return self.object |
|
106 | ||
107 |
def delete(self, request, *args, **kwargs): |
|
108 |
response = super(MapCellDeleteLayer, self).delete(request, *args, **kwargs) |
|
109 |
PageSnapshot.take( |
|
110 |
self.cell.page, |
|
111 |
request=self.request, |
|
112 |
comment=_('removed layer "%s" from cell "%s"') % (self.object.map_layer, self.cell)) |
|
113 |
return response |
|
114 | ||
115 |
def get_success_url(self): |
|
116 |
return '%s#cell-%s' % ( |
|
117 |
reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')}), |
|
118 |
self.kwargs['cell_reference']) |
|
119 | ||
120 | ||
121 |
map_cell_delete_layer = MapCellDeleteLayer.as_view() |
combo/apps/maps/migrations/0008_map_layer_options.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.db import migrations, models |
|
5 |
import django.db.models.deletion |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('maps', '0007_auto_20180706_1345'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.SeparateDatabaseAndState( |
|
16 |
state_operations=[ |
|
17 |
migrations.CreateModel( |
|
18 |
name='MapLayerOptions', |
|
19 |
fields=[ |
|
20 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|
21 |
], |
|
22 |
options={ |
|
23 |
'db_table': 'maps_map_layers', |
|
24 |
}, |
|
25 |
), |
|
26 |
migrations.AlterField( |
|
27 |
model_name='map', |
|
28 |
name='layers', |
|
29 |
field=models.ManyToManyField(blank=True, through='maps.MapLayerOptions', to='maps.MapLayer', verbose_name='Layers'), |
|
30 |
), |
|
31 |
migrations.AddField( |
|
32 |
model_name='maplayeroptions', |
|
33 |
name='map_cell', |
|
34 |
field=models.ForeignKey(db_column='map_id', on_delete=django.db.models.deletion.CASCADE, to='maps.Map'), |
|
35 |
), |
|
36 |
migrations.AddField( |
|
37 |
model_name='maplayeroptions', |
|
38 |
name='map_layer', |
|
39 |
field=models.ForeignKey(db_column='maplayer_id', verbose_name='Layer', on_delete=django.db.models.deletion.CASCADE, to='maps.MapLayer'), |
|
40 |
), |
|
41 |
migrations.AlterUniqueTogether( |
|
42 |
name='maplayeroptions', |
|
43 |
unique_together=set([('map_cell', 'map_layer')]), |
|
44 |
), |
|
45 |
], |
|
46 |
database_operations=[] |
|
47 |
) |
|
48 |
] |
combo/apps/maps/migrations/0009_map_layer_kind.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2020-02-07 09:00 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('maps', '0008_map_layer_options'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AddField( |
|
16 |
model_name='maplayer', |
|
17 |
name='kind', |
|
18 |
field=models.CharField(choices=[('tiles', 'Tiles'), ('geojson', 'GeoJSON')], default='geojson', max_length=10), |
|
19 |
), |
|
20 |
migrations.AddField( |
|
21 |
model_name='maplayer', |
|
22 |
name='tiles_attribution', |
|
23 |
field=models.CharField(blank=True, max_length=1024, null=True, verbose_name='Attribution'), |
|
24 |
), |
|
25 |
migrations.AddField( |
|
26 |
model_name='maplayer', |
|
27 |
name='tiles_default', |
|
28 |
field=models.BooleanField(default=False, verbose_name='Default tiles layer'), |
|
29 |
), |
|
30 |
migrations.AddField( |
|
31 |
model_name='maplayer', |
|
32 |
name='tiles_template_url', |
|
33 |
field=models.CharField(blank=True, max_length=1024, null=True, verbose_name='Tiles URL'), |
|
34 |
), |
|
35 |
] |
combo/apps/maps/models.py | ||
---|---|---|
267 | 267 |
group_markers = models.BooleanField(_('Group markers in clusters'), default=False) |
268 | 268 |
marker_behaviour_onclick = models.CharField(_('Marker behaviour on click'), max_length=32, |
269 | 269 |
default='none', choices=MARKER_BEHAVIOUR_ONCLICK) |
270 |
layers = models.ManyToManyField(MapLayer, verbose_name=_('Layers'), blank=True) |
|
270 |
layers = models.ManyToManyField( |
|
271 |
MapLayer, |
|
272 |
through='MapLayerOptions', |
|
273 |
verbose_name=_('Layers'), |
|
274 |
blank=True |
|
275 |
) |
|
271 | 276 | |
272 | 277 |
template_name = 'maps/map_cell.html' |
278 |
manager_form_template = 'maps/map_cell_form.html' |
|
273 | 279 | |
274 | 280 |
class Meta: |
275 | 281 |
verbose_name = _('Map') |
... | ... | |
287 | 293 | |
288 | 294 |
def get_default_form_class(self): |
289 | 295 |
fields = ('title', 'initial_state', 'initial_zoom', 'min_zoom', |
290 |
'max_zoom', 'group_markers', 'marker_behaviour_onclick', 'layers') |
|
291 |
widgets = {'layers': forms.widgets.CheckboxSelectMultiple} |
|
292 |
return forms.models.modelform_factory(self.__class__, fields=fields, |
|
293 |
widgets=widgets) |
|
296 |
'max_zoom', 'group_markers', 'marker_behaviour_onclick') |
|
297 |
return forms.models.modelform_factory(self.__class__, fields=fields) |
|
294 | 298 | |
295 | 299 |
def get_geojson(self, request): |
296 | 300 |
geojson = {'type': 'FeatureCollection', 'features': []} |
... | ... | |
302 | 306 | |
303 | 307 |
@classmethod |
304 | 308 |
def is_enabled(cls): |
305 |
return MapLayer.objects.count() > 0
|
|
309 |
return MapLayer.objects.exists()
|
|
306 | 310 | |
307 | 311 |
def get_cell_extra_context(self, context): |
308 | 312 |
ctx = super(Map, self).get_cell_extra_context(context) |
... | ... | |
322 | 326 |
ctx['marker_behaviour_onclick'] = self.marker_behaviour_onclick |
323 | 327 |
return ctx |
324 | 328 | |
329 |
def get_free_layers(self): |
|
330 |
used_layers = MapLayerOptions.objects.filter(map_cell=self).values('map_layer') |
|
331 |
return MapLayer.objects.exclude(pk__in=used_layers) |
|
332 | ||
333 |
def export_subobjects(self): |
|
334 |
return {'layers': [x.get_as_serialized_object() for x in MapLayerOptions.objects.filter(map_cell=self)]} |
|
335 | ||
336 |
@classmethod |
|
337 |
def prepare_serialized_data(cls, cell_data): |
|
338 |
# ensure compatibility with old exports |
|
339 |
if 'layers' in cell_data['fields']: |
|
340 |
layers = cell_data['fields'].pop('layers') |
|
341 |
cell_data['layers'] = [ |
|
342 |
{ |
|
343 |
'fields': {'map_layer': layer}, |
|
344 |
'model': 'maps.maplayeroptions' |
|
345 |
} for layer in layers |
|
346 |
] |
|
347 |
return cell_data |
|
348 | ||
349 |
def import_subobjects(self, cell_json): |
|
350 |
if 'layers' not in cell_json: |
|
351 |
return |
|
352 |
for layer in cell_json['layers']: |
|
353 |
layer['fields']['map_cell'] = self.pk |
|
354 |
for layer in serializers.deserialize('json', json.dumps(cell_json['layers'])): |
|
355 |
layer.save() |
|
356 | ||
325 | 357 |
def duplicate_m2m(self, new_cell): |
326 | 358 |
# set layers |
327 |
new_cell.layers.set(self.layers.all()) |
|
359 |
for layer in self.layers.all(): |
|
360 |
MapLayerOptions.objects.create(map_cell=new_cell, map_layer=layer) |
|
361 | ||
362 | ||
363 |
class MapLayerOptions(models.Model): |
|
364 |
map_cell = models.ForeignKey(Map, on_delete=models.CASCADE, db_column='map_id') |
|
365 |
map_layer = models.ForeignKey(MapLayer, verbose_name=_('Layer'), on_delete=models.CASCADE, db_column='maplayer_id') |
|
366 | ||
367 |
class Meta: |
|
368 |
db_table = 'maps_map_layers' |
|
369 |
unique_together = ('map_cell', 'map_layer') |
|
370 | ||
371 |
def get_as_serialized_object(self): |
|
372 |
serialized_options = json.loads( |
|
373 |
serializers.serialize('json', [self], use_natural_foreign_keys=True, use_natural_primary_keys=True) |
|
374 |
)[0] |
|
375 |
del serialized_options['fields']['map_cell'] |
|
376 |
del serialized_options['pk'] |
|
377 |
return serialized_options |
combo/apps/maps/templates/maps/layer_options_form.html | ||
---|---|---|
1 |
{% extends "combo/manager_base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block appbar %} |
|
5 |
{% if form.instance.pk %} |
|
6 |
<h2>{% trans "Edit layer" %}</h2> |
|
7 |
{% else %} |
|
8 |
<h2>{% trans "New layer" %}</h2> |
|
9 |
{% endif %} |
|
10 |
{% endblock %} |
|
11 | ||
12 |
{% block content %} |
|
13 | ||
14 |
<form method="post" enctype="multipart/form-data"> |
|
15 |
{% csrf_token %} |
|
16 |
{{ form.as_p }} |
|
17 |
<div class="buttons"> |
|
18 |
<button class="submit-button">{% trans "Save" %}</button> |
|
19 |
<a class="cancel" href="{% url 'combo-manager-page-view' pk=form.instance.map_cell.page_id %}">{% trans 'Cancel' %}</a> |
|
20 |
</div> |
|
21 |
</form> |
|
22 |
{% endblock %} |
combo/apps/maps/templates/maps/map_cell_form.html | ||
---|---|---|
1 |
{% extends "combo/cell_form.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block cell-form %} |
|
5 |
{{ form.as_p }} |
|
6 |
{% with cell.maplayeroptions_set.all as options %} |
|
7 |
{% if options %} |
|
8 |
<p><label>{% trans "Layers:" %}</label></p> |
|
9 |
<div> |
|
10 |
<ul class="objects-list list-of-layers"> |
|
11 |
{% for option in options %} |
|
12 |
<li> |
|
13 |
<span>{{ option.map_layer }}</span> |
|
14 |
<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> |
|
15 |
</li> |
|
16 |
{% endfor %} |
|
17 |
</ul> |
|
18 |
</div> |
|
19 |
{% endif %} |
|
20 |
{% endwith %} |
|
21 |
{% if cell.get_free_layers.exists %} |
|
22 |
<div class="buttons"> |
|
23 |
<a rel="popup" href="{% url 'maps-manager-cell-add-layer' page_pk=page.pk cell_reference=cell.get_reference %}">{% trans "Add a layer" %}</a> |
|
24 |
</div> |
|
25 |
{% endif %} |
|
26 |
{% endblock %} |
combo/apps/maps/urls.py | ||
---|---|---|
18 | 18 | |
19 | 19 |
from combo.urls_utils import decorated_includes, manager_required |
20 | 20 | |
21 |
from .manager_views import (ManagerHomeView, LayerAddView, |
|
22 |
LayerEditView, LayerDeleteView) |
|
21 |
from . import manager_views |
|
23 | 22 | |
24 | 23 |
from .views import GeojsonView |
25 | 24 | |
26 | 25 |
maps_manager_urls = [ |
27 |
url('^$', ManagerHomeView.as_view(), name='maps-manager-homepage'), |
|
28 |
url('^layers/add/$', LayerAddView.as_view(), name='maps-manager-layer-add'), |
|
29 |
url(r'^layers/(?P<slug>[\w-]+)/edit/$', LayerEditView.as_view(), |
|
26 |
url('^$', manager_views.ManagerHomeView.as_view(), name='maps-manager-homepage'),
|
|
27 |
url('^layers/add/$', manager_views.LayerAddView.as_view(), name='maps-manager-layer-add'),
|
|
28 |
url(r'^layers/(?P<slug>[\w-]+)/edit/$', manager_views.LayerEditView.as_view(),
|
|
30 | 29 |
name='maps-manager-layer-edit'), |
31 |
url(r'^layers/(?P<slug>[\w-]+)/delete/$', LayerDeleteView.as_view(), |
|
30 |
url(r'^layers/(?P<slug>[\w-]+)/delete/$', manager_views.LayerDeleteView.as_view(),
|
|
32 | 31 |
name='maps-manager-layer-delete'), |
32 |
url(r'^pages/(?P<page_pk>\d+)/cell/(?P<cell_reference>[\w_-]+)/add-layer/$', |
|
33 |
manager_views.map_cell_add_layer, |
|
34 |
name='maps-manager-cell-add-layer'), |
|
35 |
url(r'^pages/(?P<page_pk>\d+)/cell/(?P<cell_reference>[\w_-]+)/layer/(?P<layeroptions_pk>\d+)/delete/$', |
|
36 |
manager_views.map_cell_delete_layer, |
|
37 |
name='maps-manager-cell-delete-layer'), |
|
33 | 38 |
] |
34 | 39 | |
35 | 40 |
urlpatterns = [ |
combo/data/models.py | ||
---|---|---|
413 | 413 |
@classmethod |
414 | 414 |
def load_serialized_cells(cls, cells): |
415 | 415 |
# load new cells |
416 |
deserialized_cells = serializers.deserialize('json', json.dumps(cells), |
|
417 |
ignorenonexistent=True) |
|
418 |
for index, cell in enumerate(deserialized_cells): |
|
416 |
for cell_data in cells: |
|
417 |
model = apps.get_model(cell_data['model']) |
|
418 |
cell_data = model.prepare_serialized_data(cell_data) |
|
419 |
cell = list(serializers.deserialize('json', json.dumps([cell_data]), ignorenonexistent=True))[0] |
|
419 | 420 |
cell.save() |
420 | 421 |
# will populate cached_* attributes |
421 | 422 |
cell.object.save() |
422 |
cell.object.import_subobjects(cells[index])
|
|
423 |
cell.object.import_subobjects(cell_data)
|
|
423 | 424 | |
424 | 425 |
@classmethod |
425 | 426 |
def load_serialized_pages(cls, json_site): |
... | ... | |
774 | 775 |
def get_external_links_data(self): |
775 | 776 |
return [] |
776 | 777 | |
778 |
@classmethod |
|
779 |
def prepare_serialized_data(cls, cell_data): |
|
780 |
return cell_data |
|
781 | ||
777 | 782 |
def export_subobjects(self): |
778 | 783 |
return {} |
779 | 784 |
combo/manager/static/css/combo.manager.css | ||
---|---|---|
485 | 485 |
padding-left: 0; |
486 | 486 |
} |
487 | 487 | |
488 |
ul.objects-list.list-of-links li a.link-action-icon { |
|
488 |
ul.objects-list.list-of-links li a.link-action-icon, |
|
489 |
ul.objects-list.list-of-layers li a.link-action-icon { |
|
489 | 490 |
height: 100%; |
490 | 491 |
position: absolute; |
491 | 492 |
right: 0; |
... | ... | |
499 | 500 |
line-height: 3em; |
500 | 501 |
} |
501 | 502 | |
502 |
ul.objects-list.list-of-links li a.link-action-icon::before { |
|
503 |
ul.objects-list.list-of-links li a.link-action-icon::before, |
|
504 |
ul.objects-list.list-of-layers li a.link-action-icon::before { |
|
503 | 505 |
font-family: FontAwesome; |
504 | 506 |
text-indent: 0px; |
505 | 507 |
text-align: center; |
... | ... | |
507 | 509 |
width: 100%; |
508 | 510 |
} |
509 | 511 | |
510 |
ul.objects-list.list-of-links li a.link-action-icon.delete::before { |
|
512 |
ul.objects-list.list-of-links li a.link-action-icon.delete::before, |
|
513 |
ul.objects-list.list-of-layers li a.link-action-icon.delete::before { |
|
511 | 514 |
content: "\f057"; /* remove-sign */ |
512 | 515 |
} |
513 | 516 | |
514 |
ul.objects-list.list-of-links li a.link-action-icon.edit { |
|
517 |
ul.objects-list.list-of-links li a.link-action-icon.edit, |
|
518 |
ul.objects-list.list-of-layers li a.link-action-icon.edit { |
|
515 | 519 |
right: 3em; |
516 | 520 |
} |
517 | 521 | |
518 |
ul.objects-list.list-of-links li a.link-action-icon.edit::before { |
|
522 |
ul.objects-list.list-of-links li a.link-action-icon.edit::before, |
|
523 |
ul.objects-list.list-of-layers li a.link-action-icon.edit::before { |
|
519 | 524 |
content: "\f044"; |
520 | 525 |
} |
tests/test_import_export.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
from combo.apps.assets.models import Asset |
18 | 18 |
from combo.apps.gallery.models import Image, GalleryCell |
19 |
from combo.apps.maps.models import MapLayer, Map |
|
19 |
from combo.apps.maps.models import MapLayer, Map, MapLayerOptions
|
|
20 | 20 |
from combo.apps.pwa.models import PwaSettings, PwaNavigationEntry |
21 | 21 |
from combo.data.models import Page, TextCell |
22 | 22 |
from combo.data.utils import export_site, import_site, MissingGroups |
... | ... | |
123 | 123 |
import_site(data={}, if_empty=True) |
124 | 124 |
assert MapLayer.objects.count() == 2 |
125 | 125 | |
126 | ||
126 | 127 |
def test_import_export_map_cells(app, some_data, some_map_layers): |
127 | 128 |
page = Page.objects.get(slug='one') |
128 | 129 |
cell = Map(page=page, order=0, placeholder='content') |
129 | 130 |
cell.save() |
130 |
cell.layers.add(MapLayer.objects.get(slug='foo')) |
|
131 |
cell.save() |
|
131 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=MapLayer.objects.get(slug='foo')) |
|
132 | 132 |
site_export = get_output_of_command('export_site') |
133 | 133 |
import_site(data={}, clean=True) |
134 | 134 |
assert Map.objects.count() == 0 |
135 |
assert MapLayer.objects.count() == 0 |
|
135 | 136 | |
136 |
import_site(data=json.loads(site_export), clean=True) |
|
137 |
site_data = json.loads(site_export) |
|
138 |
import_site(data=site_data, clean=True) |
|
137 | 139 |
assert Map.objects.count() == 1 |
140 |
assert MapLayer.objects.filter(slug='foo').exists() is True |
|
138 | 141 |
assert Map.objects.all()[0].layers.all()[0].slug == 'foo' |
139 | 142 | |
143 |
# test old export format |
|
144 |
import_site(data={}, clean=True) |
|
145 |
assert Map.objects.count() == 0 |
|
146 |
assert MapLayer.objects.count() == 0 |
|
147 | ||
148 |
del site_data['pages'][0]['cells'][0]['layers'] |
|
149 |
site_data['pages'][0]['cells'][0]['fields']['layers'] = [['foo']] |
|
150 |
import_site(data=site_data, clean=True) |
|
151 |
assert Map.objects.count() == 1 |
|
152 |
assert MapLayer.objects.filter(slug='foo').exists() is True |
|
153 |
assert Map.objects.all()[0].layers.all()[0].slug == 'foo' |
|
154 | ||
155 | ||
140 | 156 |
def test_group_restrictions_import_export(app, some_data): |
141 | 157 |
group = Group(name='A Group') |
142 | 158 |
group.save() |
tests/test_maps_cells.py | ||
---|---|---|
9 | 9 |
from django.contrib.auth.models import Group |
10 | 10 | |
11 | 11 |
from combo.data.models import Page |
12 |
from combo.apps.maps.models import MapLayer, Map |
|
12 |
from combo.apps.maps.models import MapLayer, Map, MapLayerOptions
|
|
13 | 13 | |
14 | 14 |
from .test_manager import login |
15 | 15 | |
... | ... | |
123 | 123 |
page.save() |
124 | 124 |
cell = Map(page=page, placeholder='content', order=0, title='Map with points') |
125 | 125 |
cell.save() |
126 |
cell.layers.add(layer)
|
|
126 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
|
|
127 | 127 |
context = {'request': RequestFactory().get('/')} |
128 | 128 |
rendered = cell.render(context) |
129 | 129 |
assert 'data-init-zoom="13"' in rendered |
... | ... | |
152 | 152 |
cell = Map(page=page, placeholder='content', order=0, |
153 | 153 |
title='Map with points') |
154 | 154 |
cell.save() |
155 |
cell.layers.add(layer)
|
|
155 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
|
|
156 | 156 |
app.get(reverse('mapcell-geojson', kwargs={'cell_id': cell.id}), status=403) |
157 | 157 | |
158 | 158 |
def test_get_geojson_on_non_publik_cell(app, layer): |
... | ... | |
161 | 161 |
cell = Map(page=page, placeholder='content', order=0, public=False, |
162 | 162 |
title='Map with points') |
163 | 163 |
cell.save() |
164 |
cell.layers.add(layer)
|
|
164 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
|
|
165 | 165 |
app.get(reverse('mapcell-geojson', kwargs={'cell_id': cell.id}), status=403) |
166 | 166 | |
167 | 167 |
def test_geojson_on_restricted_cell(app, layer, user): |
... | ... | |
171 | 171 |
cell = Map(page=page, placeholder='content', order=0, public=False) |
172 | 172 |
cell.title = 'Map with points' |
173 | 173 |
cell.save() |
174 |
cell.layers.add(layer)
|
|
174 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
|
|
175 | 175 |
cell.groups.add(group) |
176 | 176 |
login(app) |
177 | 177 |
app.get(reverse('mapcell-geojson', kwargs={'cell_id': cell.id}), status=403) |
... | ... | |
193 | 193 |
cell.save() |
194 | 194 |
layer.geojson_url = 'http://example.org/geojson?t1' |
195 | 195 |
layer.save() |
196 |
cell.layers.add(layer)
|
|
196 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
|
|
197 | 197 | |
198 | 198 |
# check cache duration |
199 | 199 |
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get: |
... | ... | |
307 | 307 |
layer2.icon = 'fa-bicycle' |
308 | 308 |
layer2.icon_colour = '0000FF' |
309 | 309 |
layer2.save() |
310 |
cell.layers.add(layer2)
|
|
310 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer2)
|
|
311 | 311 | |
312 | 312 |
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get: |
313 | 313 |
requests_get.return_value = mock.Mock( |
... | ... | |
338 | 338 |
cell.title = 'Map' |
339 | 339 |
cell.save() |
340 | 340 |
layer.save() |
341 |
cell.layers.add(layer)
|
|
341 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
|
|
342 | 342 | |
343 | 343 |
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get: |
344 | 344 |
layer.geojson_url = 'http://example.org/geojson?t1' |
... | ... | |
397 | 397 |
def test_duplicate(layer): |
398 | 398 |
page = Page.objects.create(title='xxx', slug='new', template_name='standard') |
399 | 399 |
cell = Map.objects.create(page=page, placeholder='content', order=0, public=True, title='Map') |
400 |
layer.save() |
|
401 |
cell.layers.add(layer) |
|
400 |
MapLayerOptions.objects.create(map_cell=cell, map_layer=layer) |
|
402 | 401 | |
403 | 402 |
new_cell = cell.duplicate() |
404 | 403 |
assert list(new_cell.layers.all()) == [layer] |
tests/test_maps_manager.py | ||
---|---|---|
3 | 3 |
import pytest |
4 | 4 |
import mock |
5 | 5 | |
6 |
from django.contrib.auth.models import User |
|
7 | ||
8 | 6 |
from combo.apps.maps.models import Map |
9 | 7 |
from combo.apps.maps.models import MapLayer |
8 |
from combo.apps.maps.models import MapLayerOptions |
|
10 | 9 |
from combo.data.models import Page |
11 | 10 | |
12 | 11 |
pytestmark = pytest.mark.django_db |
... | ... | |
124 | 123 |
assert item['properties']['layer']['label'] == 'Test' |
125 | 124 |
assert item['properties']['layer']['colour'] == '#FFFFFF' |
126 | 125 |
assert item['properties']['layer']['icon_colour'] == '#FFFFFF' |
126 | ||
127 | ||
128 |
def test_add_delete_layer(app, admin_user): |
|
129 |
layer = MapLayer.objects.create( |
|
130 |
label='bicycles', |
|
131 |
geojson_url='http://example.org/geojson', |
|
132 |
) |
|
133 |
page = Page.objects.create(title='One', slug='one', template_name='standard') |
|
134 |
cell = Map.objects.create(page=page, placeholder='content', order=0, public=True, title='Map') |
|
135 |
app = login(app) |
|
136 |
resp = app.get('/manage/pages/%s/' % page.pk) |
|
137 | ||
138 |
assert list(cell.get_free_layers()) == [layer] |
|
139 |
resp = resp.click(href='.*/add-layer/$') |
|
140 |
resp.forms[0]['map_layer'] = layer.pk |
|
141 |
resp = resp.forms[0].submit() |
|
142 |
assert resp.status_int == 302 |
|
143 |
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference())) |
|
144 |
assert MapLayerOptions.objects.count() == 1 |
|
145 |
options = MapLayerOptions.objects.get() |
|
146 |
assert options.map_cell == cell |
|
147 |
assert options.map_layer == layer |
|
148 | ||
149 |
resp = resp.follow() |
|
150 |
assert list(cell.get_free_layers()) == [] |
|
151 |
assert '/add-layer/$' not in resp.text |
|
152 |
resp = resp.click(href='.*/layer/%s/delete/$' % options.pk) |
|
153 |
resp = resp.forms[0].submit() |
|
154 |
assert resp.status_int == 302 |
|
155 |
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference())) |
|
156 |
assert MapLayerOptions.objects.count() == 0 |
|
127 |
- |