diff --git a/combo/apps/maps/forms.py b/combo/apps/maps/forms.py
index b9473dd6..b6278b97 100644
--- a/combo/apps/maps/forms.py
+++ b/combo/apps/maps/forms.py
@@ -20,7 +20,7 @@ from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from combo.data.fields import TemplatableURLField
-from .models import MapLayer
+from .models import MapLayer, MapLayerOptions
class IconRadioSelect(forms.RadioSelect):
@@ -37,7 +37,6 @@ class MapNewLayerForm(forms.ModelForm):
'icon_colour': forms.TextInput(attrs={'type': 'color'}),
}
-
def __init__(self, *args, **kwargs):
super(MapNewLayerForm, self).__init__(*args, **kwargs)
self.fields['icon'].choices = list(
@@ -59,3 +58,13 @@ class MapLayerForm(forms.ModelForm):
super(MapLayerForm, self).__init__(*args, **kwargs)
self.fields['icon'].choices = list(
sorted(self.fields['icon'].choices, key=lambda x: slugify(force_text(x[1]))))
+
+
+class MapLayerOptionsForm(forms.ModelForm):
+ class Meta:
+ model = MapLayerOptions
+ fields = ['map_layer']
+
+ def __init__(self, *args, **kwargs):
+ super(MapLayerOptionsForm, self).__init__(*args, **kwargs)
+ self.fields['map_layer'].queryset = self.instance.map_cell.get_free_layers()
diff --git a/combo/apps/maps/manager_views.py b/combo/apps/maps/manager_views.py
index c9346778..cc6216aa 100644
--- a/combo/apps/maps/manager_views.py
+++ b/combo/apps/maps/manager_views.py
@@ -14,13 +14,17 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from django.core.urlresolvers import reverse_lazy
-from django.views.generic import (TemplateView, ListView, CreateView,
- UpdateView, DeleteView)
+from django.core.urlresolvers import reverse, reverse_lazy
+from django.http import Http404
+from django.shortcuts import get_object_or_404
+from django.utils.translation import ugettext_lazy as _
+from django.views.generic import ListView, CreateView, UpdateView, DeleteView
+from combo.data.models import CellBase, PageSnapshot
from .models import Map
from .models import MapLayer
-from .forms import MapNewLayerForm, MapLayerForm
+from .models import MapLayerOptions
+from .forms import MapNewLayerForm, MapLayerForm, MapLayerOptionsForm
class MapLayerMixin(object):
@@ -49,3 +53,69 @@ class LayerEditView(MapLayerMixin, UpdateView):
class LayerDeleteView(MapLayerMixin, DeleteView):
template_name = 'maps/map_layer_confirm_delete.html'
+
+
+class MapCellAddLayer(CreateView):
+ 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
+ return super(MapCellAddLayer, self).dispatch(request, *args, **kwargs)
+
+ def get_form_kwargs(self):
+ kwargs = super(MapCellAddLayer, self).get_form_kwargs()
+ kwargs['instance'] = MapLayerOptions(map_cell=self.cell)
+ return kwargs
+
+ def form_valid(self, form):
+ PageSnapshot.take(
+ self.cell.page,
+ request=self.request,
+ comment=_('added layer "%s" to cell "%s"') % (form.instance.map_layer, self.cell))
+ return super(MapCellAddLayer, 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_add_layer = MapCellAddLayer.as_view()
+
+
+class MapCellDeleteLayer(DeleteView):
+ template_name = 'combo/generic_confirm_delete.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(MapCellDeleteLayer, self).dispatch(request, *args, **kwargs)
+
+ def get_object(self, *args, **kwargs):
+ return self.object
+
+ def delete(self, request, *args, **kwargs):
+ response = super(MapCellDeleteLayer, self).delete(request, *args, **kwargs)
+ PageSnapshot.take(
+ self.cell.page,
+ request=self.request,
+ comment=_('removed layer "%s" from cell "%s"') % (self.object.map_layer, self.cell))
+ return response
+
+ 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_delete_layer = MapCellDeleteLayer.as_view()
diff --git a/combo/apps/maps/migrations/0008_map_layer_options.py b/combo/apps/maps/migrations/0008_map_layer_options.py
new file mode 100644
index 00000000..e0e36715
--- /dev/null
+++ b/combo/apps/maps/migrations/0008_map_layer_options.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('maps', '0007_auto_20180706_1345'),
+ ]
+
+ operations = [
+ migrations.SeparateDatabaseAndState(
+ state_operations=[
+ migrations.CreateModel(
+ name='MapLayerOptions',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ],
+ options={
+ 'db_table': 'maps_map_layers',
+ },
+ ),
+ migrations.AlterField(
+ model_name='map',
+ name='layers',
+ field=models.ManyToManyField(blank=True, through='maps.MapLayerOptions', to='maps.MapLayer', verbose_name='Layers'),
+ ),
+ migrations.AddField(
+ model_name='maplayeroptions',
+ name='map_cell',
+ field=models.ForeignKey(db_column='map_id', on_delete=django.db.models.deletion.CASCADE, to='maps.Map'),
+ ),
+ migrations.AddField(
+ model_name='maplayeroptions',
+ name='map_layer',
+ field=models.ForeignKey(db_column='maplayer_id', verbose_name='Layer', on_delete=django.db.models.deletion.CASCADE, to='maps.MapLayer'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='maplayeroptions',
+ unique_together=set([('map_cell', 'map_layer')]),
+ ),
+ ],
+ database_operations=[]
+ )
+ ]
diff --git a/combo/apps/maps/migrations/0009_map_layer_kind.py b/combo/apps/maps/migrations/0009_map_layer_kind.py
new file mode 100644
index 00000000..4b9cbcb7
--- /dev/null
+++ b/combo/apps/maps/migrations/0009_map_layer_kind.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2020-02-07 09:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('maps', '0008_map_layer_options'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='maplayer',
+ name='kind',
+ field=models.CharField(choices=[('tiles', 'Tiles'), ('geojson', 'GeoJSON')], default='geojson', max_length=10),
+ ),
+ migrations.AddField(
+ model_name='maplayer',
+ name='tiles_attribution',
+ field=models.CharField(blank=True, max_length=1024, null=True, verbose_name='Attribution'),
+ ),
+ migrations.AddField(
+ model_name='maplayer',
+ name='tiles_default',
+ field=models.BooleanField(default=False, verbose_name='Default tiles layer'),
+ ),
+ migrations.AddField(
+ model_name='maplayer',
+ name='tiles_template_url',
+ field=models.CharField(blank=True, max_length=1024, null=True, verbose_name='Tiles URL'),
+ ),
+ ]
diff --git a/combo/apps/maps/models.py b/combo/apps/maps/models.py
index 25822995..b6c08605 100644
--- a/combo/apps/maps/models.py
+++ b/combo/apps/maps/models.py
@@ -267,9 +267,15 @@ class Map(CellBase):
group_markers = models.BooleanField(_('Group markers in clusters'), default=False)
marker_behaviour_onclick = models.CharField(_('Marker behaviour on click'), max_length=32,
default='none', choices=MARKER_BEHAVIOUR_ONCLICK)
- layers = models.ManyToManyField(MapLayer, verbose_name=_('Layers'), blank=True)
+ layers = models.ManyToManyField(
+ MapLayer,
+ through='MapLayerOptions',
+ verbose_name=_('Layers'),
+ blank=True
+ )
template_name = 'maps/map_cell.html'
+ manager_form_template = 'maps/map_cell_form.html'
class Meta:
verbose_name = _('Map')
@@ -287,10 +293,8 @@ class Map(CellBase):
def get_default_form_class(self):
fields = ('title', 'initial_state', 'initial_zoom', 'min_zoom',
- 'max_zoom', 'group_markers', 'marker_behaviour_onclick', 'layers')
- widgets = {'layers': forms.widgets.CheckboxSelectMultiple}
- return forms.models.modelform_factory(self.__class__, fields=fields,
- widgets=widgets)
+ 'max_zoom', 'group_markers', 'marker_behaviour_onclick')
+ return forms.models.modelform_factory(self.__class__, fields=fields)
def get_geojson(self, request):
geojson = {'type': 'FeatureCollection', 'features': []}
@@ -302,7 +306,7 @@ class Map(CellBase):
@classmethod
def is_enabled(cls):
- return MapLayer.objects.count() > 0
+ return MapLayer.objects.exists()
def get_cell_extra_context(self, context):
ctx = super(Map, self).get_cell_extra_context(context)
@@ -322,6 +326,52 @@ class Map(CellBase):
ctx['marker_behaviour_onclick'] = self.marker_behaviour_onclick
return ctx
+ def get_free_layers(self):
+ used_layers = MapLayerOptions.objects.filter(map_cell=self).values('map_layer')
+ return MapLayer.objects.exclude(pk__in=used_layers)
+
+ def export_subobjects(self):
+ return {'layers': [x.get_as_serialized_object() for x in MapLayerOptions.objects.filter(map_cell=self)]}
+
+ @classmethod
+ def prepare_serialized_data(cls, cell_data):
+ # ensure compatibility with old exports
+ if 'layers' in cell_data['fields']:
+ layers = cell_data['fields'].pop('layers')
+ cell_data['layers'] = [
+ {
+ 'fields': {'map_layer': layer},
+ 'model': 'maps.maplayeroptions'
+ } for layer in layers
+ ]
+ return cell_data
+
+ def import_subobjects(self, cell_json):
+ if 'layers' not in cell_json:
+ return
+ for layer in cell_json['layers']:
+ layer['fields']['map_cell'] = self.pk
+ for layer in serializers.deserialize('json', json.dumps(cell_json['layers'])):
+ layer.save()
+
def duplicate_m2m(self, new_cell):
# set layers
- new_cell.layers.set(self.layers.all())
+ for layer in self.layers.all():
+ MapLayerOptions.objects.create(map_cell=new_cell, map_layer=layer)
+
+
+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')
+
+ class Meta:
+ db_table = 'maps_map_layers'
+ unique_together = ('map_cell', 'map_layer')
+
+ def get_as_serialized_object(self):
+ serialized_options = json.loads(
+ serializers.serialize('json', [self], use_natural_foreign_keys=True, use_natural_primary_keys=True)
+ )[0]
+ del serialized_options['fields']['map_cell']
+ del serialized_options['pk']
+ return serialized_options
diff --git a/combo/apps/maps/templates/maps/layer_options_form.html b/combo/apps/maps/templates/maps/layer_options_form.html
new file mode 100644
index 00000000..aa4b38ec
--- /dev/null
+++ b/combo/apps/maps/templates/maps/layer_options_form.html
@@ -0,0 +1,22 @@
+{% extends "combo/manager_base.html" %}
+{% load i18n %}
+
+{% block appbar %}
+{% if form.instance.pk %}
+
{% trans "Edit layer" %}
+{% else %}
+{% trans "New layer" %}
+{% endif %}
+{% endblock %}
+
+{% block content %}
+
+
+{% endblock %}
diff --git a/combo/apps/maps/templates/maps/map_cell_form.html b/combo/apps/maps/templates/maps/map_cell_form.html
new file mode 100644
index 00000000..003bfec6
--- /dev/null
+++ b/combo/apps/maps/templates/maps/map_cell_form.html
@@ -0,0 +1,26 @@
+{% extends "combo/cell_form.html" %}
+{% load i18n %}
+
+{% block cell-form %}
+{{ form.as_p }}
+{% with cell.maplayeroptions_set.all as options %}
+{% if options %}
+
+
+{% endif %}
+{% endwith %}
+{% if cell.get_free_layers.exists %}
+
+{% endif %}
+{% endblock %}
diff --git a/combo/apps/maps/urls.py b/combo/apps/maps/urls.py
index 6f58cb2c..e47cb4ac 100644
--- a/combo/apps/maps/urls.py
+++ b/combo/apps/maps/urls.py
@@ -18,18 +18,23 @@ from django.conf.urls import url, include
from combo.urls_utils import decorated_includes, manager_required
-from .manager_views import (ManagerHomeView, LayerAddView,
- LayerEditView, LayerDeleteView)
+from . import manager_views
from .views import GeojsonView
maps_manager_urls = [
- url('^$', ManagerHomeView.as_view(), name='maps-manager-homepage'),
- url('^layers/add/$', LayerAddView.as_view(), name='maps-manager-layer-add'),
- url(r'^layers/(?P[\w-]+)/edit/$', LayerEditView.as_view(),
+ url('^$', manager_views.ManagerHomeView.as_view(), name='maps-manager-homepage'),
+ url('^layers/add/$', manager_views.LayerAddView.as_view(), name='maps-manager-layer-add'),
+ url(r'^layers/(?P[\w-]+)/edit/$', manager_views.LayerEditView.as_view(),
name='maps-manager-layer-edit'),
- url(r'^layers/(?P[\w-]+)/delete/$', LayerDeleteView.as_view(),
+ url(r'^layers/(?P[\w-]+)/delete/$', manager_views.LayerDeleteView.as_view(),
name='maps-manager-layer-delete'),
+ url(r'^pages/(?P\d+)/cell/(?P[\w_-]+)/add-layer/$',
+ manager_views.map_cell_add_layer,
+ name='maps-manager-cell-add-layer'),
+ url(r'^pages/(?P\d+)/cell/(?P[\w_-]+)/layer/(?P\d+)/delete/$',
+ manager_views.map_cell_delete_layer,
+ name='maps-manager-cell-delete-layer'),
]
urlpatterns = [
diff --git a/combo/data/models.py b/combo/data/models.py
index c587943c..00867d94 100644
--- a/combo/data/models.py
+++ b/combo/data/models.py
@@ -413,13 +413,14 @@ class Page(models.Model):
@classmethod
def load_serialized_cells(cls, cells):
# load new cells
- deserialized_cells = serializers.deserialize('json', json.dumps(cells),
- ignorenonexistent=True)
- for index, cell in enumerate(deserialized_cells):
+ for cell_data in cells:
+ model = apps.get_model(cell_data['model'])
+ cell_data = model.prepare_serialized_data(cell_data)
+ cell = list(serializers.deserialize('json', json.dumps([cell_data]), ignorenonexistent=True))[0]
cell.save()
# will populate cached_* attributes
cell.object.save()
- cell.object.import_subobjects(cells[index])
+ cell.object.import_subobjects(cell_data)
@classmethod
def load_serialized_pages(cls, json_site):
@@ -774,6 +775,10 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
def get_external_links_data(self):
return []
+ @classmethod
+ def prepare_serialized_data(cls, cell_data):
+ return cell_data
+
def export_subobjects(self):
return {}
diff --git a/combo/manager/static/css/combo.manager.css b/combo/manager/static/css/combo.manager.css
index eaf78645..3b581a0b 100644
--- a/combo/manager/static/css/combo.manager.css
+++ b/combo/manager/static/css/combo.manager.css
@@ -485,7 +485,8 @@ ul.objects-list.list-of-links li {
padding-left: 0;
}
-ul.objects-list.list-of-links li a.link-action-icon {
+ul.objects-list.list-of-links li a.link-action-icon,
+ul.objects-list.list-of-layers li a.link-action-icon {
height: 100%;
position: absolute;
right: 0;
@@ -499,7 +500,8 @@ ul.objects-list.list-of-links li a.link-action-icon {
line-height: 3em;
}
-ul.objects-list.list-of-links li a.link-action-icon::before {
+ul.objects-list.list-of-links li a.link-action-icon::before,
+ul.objects-list.list-of-layers li a.link-action-icon::before {
font-family: FontAwesome;
text-indent: 0px;
text-align: center;
@@ -507,14 +509,17 @@ ul.objects-list.list-of-links li a.link-action-icon::before {
width: 100%;
}
-ul.objects-list.list-of-links li a.link-action-icon.delete::before {
+ul.objects-list.list-of-links li a.link-action-icon.delete::before,
+ul.objects-list.list-of-layers li a.link-action-icon.delete::before {
content: "\f057"; /* remove-sign */
}
-ul.objects-list.list-of-links li a.link-action-icon.edit {
+ul.objects-list.list-of-links li a.link-action-icon.edit,
+ul.objects-list.list-of-layers li a.link-action-icon.edit {
right: 3em;
}
-ul.objects-list.list-of-links li a.link-action-icon.edit::before {
+ul.objects-list.list-of-links li a.link-action-icon.edit::before,
+ul.objects-list.list-of-layers li a.link-action-icon.edit::before {
content: "\f044";
}
diff --git a/tests/test_import_export.py b/tests/test_import_export.py
index ff1bb652..3e55dee4 100644
--- a/tests/test_import_export.py
+++ b/tests/test_import_export.py
@@ -16,7 +16,7 @@ from django.utils.six import BytesIO, StringIO
from combo.apps.assets.models import Asset
from combo.apps.gallery.models import Image, GalleryCell
-from combo.apps.maps.models import MapLayer, Map
+from combo.apps.maps.models import MapLayer, Map, MapLayerOptions
from combo.apps.pwa.models import PwaSettings, PwaNavigationEntry
from combo.data.models import Page, TextCell
from combo.data.utils import export_site, import_site, MissingGroups
@@ -123,20 +123,36 @@ def test_import_export_map_layers(app, some_map_layers):
import_site(data={}, if_empty=True)
assert MapLayer.objects.count() == 2
+
def test_import_export_map_cells(app, some_data, some_map_layers):
page = Page.objects.get(slug='one')
cell = Map(page=page, order=0, placeholder='content')
cell.save()
- cell.layers.add(MapLayer.objects.get(slug='foo'))
- cell.save()
+ MapLayerOptions.objects.create(map_cell=cell, map_layer=MapLayer.objects.get(slug='foo'))
site_export = get_output_of_command('export_site')
import_site(data={}, clean=True)
assert Map.objects.count() == 0
+ assert MapLayer.objects.count() == 0
- import_site(data=json.loads(site_export), clean=True)
+ site_data = json.loads(site_export)
+ import_site(data=site_data, clean=True)
assert Map.objects.count() == 1
+ assert MapLayer.objects.filter(slug='foo').exists() is True
assert Map.objects.all()[0].layers.all()[0].slug == 'foo'
+ # test old export format
+ import_site(data={}, clean=True)
+ assert Map.objects.count() == 0
+ assert MapLayer.objects.count() == 0
+
+ del site_data['pages'][0]['cells'][0]['layers']
+ site_data['pages'][0]['cells'][0]['fields']['layers'] = [['foo']]
+ import_site(data=site_data, clean=True)
+ assert Map.objects.count() == 1
+ assert MapLayer.objects.filter(slug='foo').exists() is True
+ assert Map.objects.all()[0].layers.all()[0].slug == 'foo'
+
+
def test_group_restrictions_import_export(app, some_data):
group = Group(name='A Group')
group.save()
diff --git a/tests/test_maps_cells.py b/tests/test_maps_cells.py
index 368b010a..bee63f9e 100644
--- a/tests/test_maps_cells.py
+++ b/tests/test_maps_cells.py
@@ -9,7 +9,7 @@ from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group
from combo.data.models import Page
-from combo.apps.maps.models import MapLayer, Map
+from combo.apps.maps.models import MapLayer, Map, MapLayerOptions
from .test_manager import login
@@ -123,7 +123,7 @@ def test_cell_rendering(app, layer):
page.save()
cell = Map(page=page, placeholder='content', order=0, title='Map with points')
cell.save()
- cell.layers.add(layer)
+ MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
context = {'request': RequestFactory().get('/')}
rendered = cell.render(context)
assert 'data-init-zoom="13"' in rendered
@@ -152,7 +152,7 @@ def test_get_geojson_on_non_public_page(app, layer):
cell = Map(page=page, placeholder='content', order=0,
title='Map with points')
cell.save()
- cell.layers.add(layer)
+ MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
app.get(reverse('mapcell-geojson', kwargs={'cell_id': cell.id}), status=403)
def test_get_geojson_on_non_publik_cell(app, layer):
@@ -161,7 +161,7 @@ def test_get_geojson_on_non_publik_cell(app, layer):
cell = Map(page=page, placeholder='content', order=0, public=False,
title='Map with points')
cell.save()
- cell.layers.add(layer)
+ MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
app.get(reverse('mapcell-geojson', kwargs={'cell_id': cell.id}), status=403)
def test_geojson_on_restricted_cell(app, layer, user):
@@ -171,7 +171,7 @@ def test_geojson_on_restricted_cell(app, layer, user):
cell = Map(page=page, placeholder='content', order=0, public=False)
cell.title = 'Map with points'
cell.save()
- cell.layers.add(layer)
+ MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
cell.groups.add(group)
login(app)
app.get(reverse('mapcell-geojson', kwargs={'cell_id': cell.id}), status=403)
@@ -193,7 +193,7 @@ def test_get_geojson(app, layer, user):
cell.save()
layer.geojson_url = 'http://example.org/geojson?t1'
layer.save()
- cell.layers.add(layer)
+ MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
# check cache duration
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get:
@@ -307,7 +307,7 @@ def test_get_geojson(app, layer, user):
layer2.icon = 'fa-bicycle'
layer2.icon_colour = '0000FF'
layer2.save()
- cell.layers.add(layer2)
+ MapLayerOptions.objects.create(map_cell=cell, map_layer=layer2)
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get:
requests_get.return_value = mock.Mock(
@@ -338,7 +338,7 @@ def test_get_geojson_properties(app, layer, user):
cell.title = 'Map'
cell.save()
layer.save()
- cell.layers.add(layer)
+ MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as requests_get:
layer.geojson_url = 'http://example.org/geojson?t1'
@@ -397,8 +397,7 @@ def test_get_geojson_properties(app, layer, user):
def test_duplicate(layer):
page = Page.objects.create(title='xxx', slug='new', template_name='standard')
cell = Map.objects.create(page=page, placeholder='content', order=0, public=True, title='Map')
- layer.save()
- cell.layers.add(layer)
+ MapLayerOptions.objects.create(map_cell=cell, map_layer=layer)
new_cell = cell.duplicate()
assert list(new_cell.layers.all()) == [layer]
diff --git a/tests/test_maps_manager.py b/tests/test_maps_manager.py
index 1c54d73f..c6b57167 100644
--- a/tests/test_maps_manager.py
+++ b/tests/test_maps_manager.py
@@ -3,10 +3,9 @@
import pytest
import mock
-from django.contrib.auth.models import User
-
from combo.apps.maps.models import Map
from combo.apps.maps.models import MapLayer
+from combo.apps.maps.models import MapLayerOptions
from combo.data.models import Page
pytestmark = pytest.mark.django_db
@@ -124,3 +123,34 @@ def test_download_geojson(mock_request, app, admin_user):
assert item['properties']['layer']['label'] == 'Test'
assert item['properties']['layer']['colour'] == '#FFFFFF'
assert item['properties']['layer']['icon_colour'] == '#FFFFFF'
+
+
+def test_add_delete_layer(app, admin_user):
+ layer = MapLayer.objects.create(
+ label='bicycles',
+ geojson_url='http://example.org/geojson',
+ )
+ page = Page.objects.create(title='One', slug='one', template_name='standard')
+ cell = Map.objects.create(page=page, placeholder='content', order=0, public=True, title='Map')
+ app = login(app)
+ resp = app.get('/manage/pages/%s/' % page.pk)
+
+ assert list(cell.get_free_layers()) == [layer]
+ resp = resp.click(href='.*/add-layer/$')
+ resp.forms[0]['map_layer'] = layer.pk
+ resp = resp.forms[0].submit()
+ assert resp.status_int == 302
+ assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
+ assert MapLayerOptions.objects.count() == 1
+ options = MapLayerOptions.objects.get()
+ assert options.map_cell == cell
+ assert options.map_layer == layer
+
+ resp = resp.follow()
+ assert list(cell.get_free_layers()) == []
+ assert '/add-layer/$' not in resp.text
+ resp = resp.click(href='.*/layer/%s/delete/$' % options.pk)
+ resp = resp.forms[0].submit()
+ assert resp.status_int == 302
+ assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
+ assert MapLayerOptions.objects.count() == 0