From 2fd2fb3b03b686d0ae4042c1e6fd970ec077916a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 10 Feb 2020 16:02:44 +0100
Subject: [PATCH 3/5] maps: add tiles layer (#22639)

---
 combo/apps/maps/forms.py                      |  56 ++++---
 combo/apps/maps/manager_views.py              |  11 +-
 .../maps/migrations/0009_map_layer_kind.py    |  16 +-
 combo/apps/maps/models.py                     |  36 ++++-
 .../templates/maps/layer_options_form.html    |   4 +-
 .../maps/templates/maps/manager_home.html     |   5 +-
 .../maps/templates/maps/map_cell_form.html    |  15 +-
 combo/apps/maps/urls.py                       |   4 +-
 tests/test_maps_manager.py                    | 153 +++++++++++++++---
 9 files changed, 238 insertions(+), 62 deletions(-)

diff --git a/combo/apps/maps/forms.py b/combo/apps/maps/forms.py
index b6278b9..d8d8c26 100644
--- a/combo/apps/maps/forms.py
+++ b/combo/apps/maps/forms.py
@@ -27,37 +27,47 @@ class IconRadioSelect(forms.RadioSelect):
     option_template_name = 'maps/icon_radio_option.html'
 
 
-class MapNewLayerForm(forms.ModelForm):
+class MapLayerForm(forms.ModelForm):
     geojson_url = TemplatableURLField(label=_('Geojson URL'))
 
     class Meta:
         model = MapLayer
-        exclude = ('slug', 'cache_duration', 'include_user_identifier')
-        widgets = {'marker_colour': forms.TextInput(attrs={'type': 'color'}),
-                   'icon_colour': forms.TextInput(attrs={'type': 'color'}),
-                   }
+        exclude = ('kind',)
+        widgets = {
+            'marker_colour': forms.TextInput(attrs={'type': 'color'}),
+            'icon_colour': forms.TextInput(attrs={'type': 'color'}),
+        }
 
     def __init__(self, *args, **kwargs):
-        super(MapNewLayerForm, self).__init__(*args, **kwargs)
+        super(MapLayerForm, self).__init__(*args, **kwargs)
+        if self.instance.pk is None:
+            # new instance, delete some fields
+            del self.fields['slug']
+            del self.fields['cache_duration']
+            del self.fields['include_user_identifier']
+        else:
+            # new widget for icon field
+            self.fields['icon'].widget = IconRadioSelect()
         self.fields['icon'].choices = list(
                 sorted(self.fields['icon'].choices, key=lambda x: slugify(force_text(x[1]))))
+        if self.instance.kind == 'geojson':
+            todelete_fields = ['tiles_template_url', 'tiles_attribution', 'tiles_default']
+        else:
+            todelete_fields = [
+                'geojson_url', 'marker_colour', 'icon', 'icon_colour', 'cache_duration',
+                'include_user_identifier', 'properties']
+        for field in todelete_fields:
+            if field in self.fields:
+                del self.fields[field]
 
+    def clean(self):
+        cleaned_data = super(MapLayerForm, self).clean()
 
-class MapLayerForm(forms.ModelForm):
-    geojson_url = TemplatableURLField(label=_('Geojson URL'))
+        if self.instance.kind == 'tiles':
+            if MapLayer.objects.filter(kind='tiles', tiles_default=True).exclude(pk=self.instance.pk):
+                raise forms.ValidationError(_('Only one default tiles layer can be defined.'))
 
-    class Meta:
-        model = MapLayer
-        fields = '__all__'
-        widgets = {'marker_colour': forms.TextInput(attrs={'type': 'color'}),
-                   'icon_colour': forms.TextInput(attrs={'type': 'color'}),
-                   'icon': IconRadioSelect(),
-                   }
-
-    def __init__(self, *args, **kwargs):
-        super(MapLayerForm, self).__init__(*args, **kwargs)
-        self.fields['icon'].choices = list(
-                sorted(self.fields['icon'].choices, key=lambda x: slugify(force_text(x[1]))))
+        return cleaned_data
 
 
 class MapLayerOptionsForm(forms.ModelForm):
@@ -66,5 +76,9 @@ class MapLayerOptionsForm(forms.ModelForm):
         fields = ['map_layer']
 
     def __init__(self, *args, **kwargs):
+        self.kind = kwargs.pop('kind')
         super(MapLayerOptionsForm, self).__init__(*args, **kwargs)
-        self.fields['map_layer'].queryset = self.instance.map_cell.get_free_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()
diff --git a/combo/apps/maps/manager_views.py b/combo/apps/maps/manager_views.py
index 7b61eb4..627d6f4 100644
--- a/combo/apps/maps/manager_views.py
+++ b/combo/apps/maps/manager_views.py
@@ -24,7 +24,7 @@ from combo.data.models import CellBase, PageSnapshot
 from .models import Map
 from .models import MapLayer
 from .models import MapLayerOptions
-from .forms import MapNewLayerForm, MapLayerForm, MapLayerOptionsForm
+from .forms import MapLayerForm, MapLayerOptionsForm
 
 
 class MapLayerMixin(object):
@@ -34,6 +34,7 @@ class MapLayerMixin(object):
 
 class ManagerHomeView(MapLayerMixin, ListView):
     template_name = 'maps/manager_home.html'
+    ordering = ['kind', 'label']
 
     def get_context_data(self, **kwargs):
         context = super(ManagerHomeView, self).get_context_data(**kwargs)
@@ -42,9 +43,14 @@ class ManagerHomeView(MapLayerMixin, ListView):
 
 
 class LayerAddView(MapLayerMixin, CreateView):
-    form_class = MapNewLayerForm
+    form_class = MapLayerForm
     template_name = 'maps/map_layer_form.html'
 
+    def get_form_kwargs(self):
+        kwargs = super(LayerAddView, self).get_form_kwargs()
+        kwargs['instance'] = self.model(kind=self.kwargs['kind'])
+        return kwargs
+
 
 class LayerEditView(MapLayerMixin, UpdateView):
     form_class = MapLayerForm
@@ -69,6 +75,7 @@ class MapCellAddLayer(CreateView):
     def get_form_kwargs(self):
         kwargs = super(MapCellAddLayer, self).get_form_kwargs()
         kwargs['instance'] = MapLayerOptions(map_cell=self.cell)
+        kwargs['kind'] = self.kwargs['kind']
         return kwargs
 
     def form_valid(self, form):
diff --git a/combo/apps/maps/migrations/0009_map_layer_kind.py b/combo/apps/maps/migrations/0009_map_layer_kind.py
index 4b9cbcb..c5d0377 100644
--- a/combo/apps/maps/migrations/0009_map_layer_kind.py
+++ b/combo/apps/maps/migrations/0009_map_layer_kind.py
@@ -20,7 +20,14 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='maplayer',
             name='tiles_attribution',
-            field=models.CharField(blank=True, max_length=1024, null=True, verbose_name='Attribution'),
+            field=models.CharField(
+                blank=True,
+                help_text=(
+                    'For example: Map data &amp;copy; &lt;a href=&quot;https://openstreetmap.org&quot;&gt;OpenStreetMap&lt;/a&gt; contributors, '
+                    '&lt;a href=&quot;http://creativecommons.org/licenses/by-sa/2.0/&quot;&gt;CC-BY-SA&lt;/a&gt;'),
+                max_length=1024,
+                null=True,
+                verbose_name='Attribution'),
         ),
         migrations.AddField(
             model_name='maplayer',
@@ -30,6 +37,11 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='maplayer',
             name='tiles_template_url',
-            field=models.CharField(blank=True, max_length=1024, null=True, verbose_name='Tiles URL'),
+            field=models.CharField(
+                blank=True,
+                help_text='For example: https://tiles.entrouvert.org/hdm/{z}/{x}/{y}.png',
+                max_length=1024,
+                null=True,
+                verbose_name='Tiles URL'),
         ),
     ]
diff --git a/combo/apps/maps/models.py b/combo/apps/maps/models.py
index 5edf80d..d5f1569 100644
--- a/combo/apps/maps/models.py
+++ b/combo/apps/maps/models.py
@@ -20,6 +20,7 @@ from django.core import serializers
 from django.db import models
 from django.utils import six
 from django.utils.encoding import python_2_unicode_compatible
+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
@@ -33,6 +34,11 @@ from combo.data.library import register_cell_class
 from combo.utils import requests, get_templated_url
 
 
+KIND = [
+    ('tiles', _('Tiles')),
+    ('geojson', _('GeoJSON')),
+]
+
 ICONS = [
     ('ambulance', _('Ambulance')),
     ('asterisk', _('Asterisk')),
@@ -96,6 +102,21 @@ class MapLayer(models.Model):
 
     label = models.CharField(_('Label'), max_length=128)
     slug = models.SlugField(_('Identifier'))
+
+    kind = models.CharField(max_length=10, choices=KIND, default='geojson')
+
+    tiles_template_url = models.CharField(
+        _('Tiles URL'),
+        max_length=1024,
+        help_text=_('For example: %s') % settings.COMBO_MAP_TILE_URLTEMPLATE,
+        blank=True, null=True)
+    tiles_attribution = models.CharField(
+        _('Attribution'),
+        max_length=1024,
+        help_text=_('For example: %s') % escape(settings.COMBO_MAP_ATTRIBUTION),
+        blank=True, null=True)
+    tiles_default = models.BooleanField(_('Default tiles layer'), default=False)
+
     geojson_url = models.CharField(_('Geojson URL'), max_length=1024)
     marker_colour = models.CharField(_('Marker colour'), max_length=7, default='#0000FF')
     icon = models.CharField(_('Marker icon'), max_length=32, blank=True, null=True,
@@ -298,7 +319,7 @@ class Map(CellBase):
 
     def get_geojson(self, request):
         geojson = {'type': 'FeatureCollection', 'features': []}
-        layers = self.layers.all()
+        layers = self.layers.filter(kind='geojson')
         for layer in layers:
             geojson['features'] += layer.get_geojson(request,
                     multiple_layers=bool(len(layers) > 1))
@@ -326,9 +347,16 @@ 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 get_maplayer_options(self):
+        return self.maplayeroptions_set.order_by('map_layer__kind', 'map_layer__label')
+
+    def get_free_geojson_layers(self):
+        used_layers = self.maplayeroptions_set.values('map_layer')
+        return MapLayer.objects.filter(kind='geojson').exclude(pk__in=used_layers)
+
+    def get_free_tiles_layers(self):
+        used_layers = self.maplayeroptions_set.values('map_layer')
+        return MapLayer.objects.filter(kind='tiles').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)]}
diff --git a/combo/apps/maps/templates/maps/layer_options_form.html b/combo/apps/maps/templates/maps/layer_options_form.html
index aa4b38e..e044c63 100644
--- a/combo/apps/maps/templates/maps/layer_options_form.html
+++ b/combo/apps/maps/templates/maps/layer_options_form.html
@@ -3,9 +3,9 @@
 
 {% block appbar %}
 {% if form.instance.pk %}
-<h2>{% trans "Edit layer" %}</h2>
+<h2>{% if form.kind == 'geojson' %}{% trans "Edit GeoJSON layer" %}{% else %}{% trans "Edit tiles layer" %}{% endif %}</h2>
 {% else %}
-<h2>{% trans "New layer" %}</h2>
+<h2>{% if form.kind == 'geojson' %}{% trans "New GeoJSON layer" %}{% else %}{% trans "New tiles layer" %}{% endif %}</h2>
 {% endif %}
 {% endblock %}
 
diff --git a/combo/apps/maps/templates/maps/manager_home.html b/combo/apps/maps/templates/maps/manager_home.html
index 281dcb1..3747150 100644
--- a/combo/apps/maps/templates/maps/manager_home.html
+++ b/combo/apps/maps/templates/maps/manager_home.html
@@ -4,7 +4,8 @@
 {% block appbar %}
 <h2>{% trans 'Maps' %}</h2>
 <span class="actions">
-<a rel="popup" href="{% url 'maps-manager-layer-add' %}">{% trans 'New' %}</a>
+<a rel="popup" href="{% url 'maps-manager-layer-add' kind='geojson' %}">{% trans 'New GeoJSON layer' %}</a>
+<a rel="popup" href="{% url 'maps-manager-layer-add' kind='tiles' %}">{% trans 'New tiles layer' %}</a>
 </span>
 {% endblock %}
 
@@ -13,7 +14,7 @@
 <ul class="objects-list single-links layers">
  {% for layer in object_list %}
  <li>
-  <a class="layer-icon-{{ layer.icon }}" href="{% url 'maps-manager-layer-edit' slug=layer.slug %}">{{ layer.label }}</a>
+  <a class="layer-icon-{{ layer.icon }}" href="{% url 'maps-manager-layer-edit' slug=layer.slug %}">{{ layer.label }} ({{ layer.get_kind_display }}{% if layer.tiles_default %} - {% trans "default layer" %}{% endif %})</a>
   <a rel="popup" class="delete" href="{% url 'maps-manager-layer-delete' slug=layer.slug %}">{% trans "remove" %}</a>
  </li>
  {% endfor %}
diff --git a/combo/apps/maps/templates/maps/map_cell_form.html b/combo/apps/maps/templates/maps/map_cell_form.html
index 003bfec..376691f 100644
--- a/combo/apps/maps/templates/maps/map_cell_form.html
+++ b/combo/apps/maps/templates/maps/map_cell_form.html
@@ -3,14 +3,14 @@
 
 {% block cell-form %}
 {{ form.as_p }}
-{% with cell.maplayeroptions_set.all as options %}
+{% with cell.get_maplayer_options as options %}
 {% if options %}
 <p><label>{% trans "Layers:" %}</label></p>
 <div>
   <ul class="objects-list list-of-layers">
     {% for option in options %}
     <li>
-      <span>{{ option.map_layer }}</span>
+      <span>{{ option.map_layer.label }} ({{ option.map_layer.get_kind_display }})</span>
       <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 %}
@@ -18,9 +18,16 @@
 </div>
 {% endif %}
 {% endwith %}
-{% if cell.get_free_layers.exists %}
+{% with cell.get_free_geojson_layers.exists as free_geojson and cell.get_free_tiles_layers.exists as free_tiles %}
+{% if free_geojson or free_tiles %}
 <div class="buttons">
-    <a rel="popup" href="{% url 'maps-manager-cell-add-layer' page_pk=page.pk cell_reference=cell.get_reference %}">{% trans "Add a layer" %}</a>
+    {% if free_geojson %}
+    <a rel="popup" href="{% url 'maps-manager-cell-add-layer' page_pk=page.pk cell_reference=cell.get_reference kind='geojson' %}">{% trans "Add a GeoJSON layer" %}</a>
+    {% endif %}
+    {% if free_tiles %}
+    {% if free_geojson %}|{% endif%} <a rel="popup" href="{% url 'maps-manager-cell-add-layer' page_pk=page.pk cell_reference=cell.get_reference kind='tiles' %}">{% trans "Add a tiles layer" %}</a>
+    {% endif %}
 </div>
 {% endif %}
+{% endwith %}
 {% endblock %}
diff --git a/combo/apps/maps/urls.py b/combo/apps/maps/urls.py
index e47cb4a..c26f29c 100644
--- a/combo/apps/maps/urls.py
+++ b/combo/apps/maps/urls.py
@@ -24,12 +24,12 @@ from .views import GeojsonView
 
 maps_manager_urls = [
     url('^$', manager_views.ManagerHomeView.as_view(), name='maps-manager-homepage'),
-    url('^layers/add/$', manager_views.LayerAddView.as_view(), name='maps-manager-layer-add'),
+    url('^layers/add/(?P<kind>geojson|tiles)/$', manager_views.LayerAddView.as_view(), name='maps-manager-layer-add'),
     url(r'^layers/(?P<slug>[\w-]+)/edit/$', manager_views.LayerEditView.as_view(),
         name='maps-manager-layer-edit'),
     url(r'^layers/(?P<slug>[\w-]+)/delete/$', manager_views.LayerDeleteView.as_view(),
         name='maps-manager-layer-delete'),
-    url(r'^pages/(?P<page_pk>\d+)/cell/(?P<cell_reference>[\w_-]+)/add-layer/$',
+    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+)/delete/$',
diff --git a/tests/test_maps_manager.py b/tests/test_maps_manager.py
index c6b5716..c751878 100644
--- a/tests/test_maps_manager.py
+++ b/tests/test_maps_manager.py
@@ -11,6 +11,29 @@ from combo.data.models import Page
 pytestmark = pytest.mark.django_db
 
 
+@pytest.fixture
+def layer():
+    return MapLayer.objects.create(
+        label='Test',
+        kind='geojson',
+        geojson_url='http://example.org/geojson',
+        icon='bicycle',
+        marker_colour='#FFFFFF',
+        icon_colour='#FFFFFF',
+    )
+
+
+@pytest.fixture
+def tiles_layer():
+    return MapLayer.objects.create(
+        label='Test2',
+        kind='tiles',
+        tiles_template_url='http://somedomain.com/blabla/{z}/{x}/{y}{r}.png',
+        tiles_attribution='Foo bar',
+        tiles_default=True,
+    )
+
+
 def login(app, username='admin', password='admin'):
     login_page = app.get('/login/')
     login_form = login_page.forms[0]
@@ -20,16 +43,23 @@ def login(app, username='admin', password='admin'):
     assert resp.status_int == 302
     return app
 
+
 def test_access(app, admin_user):
     app = login(app)
     resp = app.get('/manage/', status=200)
     assert '/manage/maps/' in resp.text
 
-def test_add_layer(app, admin_user):
-    MapLayer.objects.all().delete()
+
+def test_add_geojson_layer(app, admin_user):
     app = login(app)
     resp = app.get('/manage/maps/', status=200)
-    resp = resp.click('New')
+    resp = resp.click('New GeoJSON layer')
+    assert 'slug' not in resp.context['form']
+    assert 'cache_duration' not in resp.context['form']
+    assert 'include_user_identifier' not in resp.context['form']
+    assert 'tiles_template_url' not in resp.context['form']
+    assert 'tiles_attribution' not in resp.context['form']
+    assert 'tiles_default' not in resp.context['form']
     resp.forms[0]['label'] = 'Test'
     resp.forms[0]['geojson_url'] = 'http://example.net/geojson'
     assert resp.form['marker_colour'].value == '#0000FF'
@@ -43,22 +73,79 @@ def test_add_layer(app, admin_user):
     layer = MapLayer.objects.get()
     assert layer.label == 'Test'
     assert layer.slug == 'test'
+    assert layer.kind == 'geojson'
+
+
+def test_add_tiles_layer(app, admin_user):
+    app = login(app)
+    resp = app.get('/manage/maps/', status=200)
+    resp = resp.click('New tiles layer')
+    assert 'slug' not in resp.context['form']
+    assert 'cache_duration' not in resp.context['form']
+    assert 'include_user_identifier' not in resp.context['form']
+    assert 'geojson_url' not in resp.context['form']
+    assert 'marker_colour' not in resp.context['form']
+    assert 'icon' not in resp.context['form']
+    assert 'icon_colour' not in resp.context['form']
+    assert 'properties' not in resp.context['form']
+    resp.forms[0]['label'] = 'Test'
+    resp.forms[0]['tiles_template_url'] = 'http://somedomain.com/blabla/{z}/{x}/{y}{r}.png'
+    resp.forms[0]['tiles_attribution'] = 'Foo bar'
+    resp.forms[0]['tiles_default'] = True
+    resp = resp.forms[0].submit()
+    assert resp.location.endswith('/manage/maps/')
+    assert MapLayer.objects.count() == 1
+    layer = MapLayer.objects.get()
+    assert layer.label == 'Test'
+    assert layer.slug == 'test'
+    assert layer.kind == 'tiles'
+    assert layer.tiles_default is True
 
-def test_edit_layer(app, admin_user):
-    test_add_layer(app, admin_user)
+    # only one default layer
+    resp = app.get('/manage/maps/', status=200)
+    resp = resp.click('New tiles layer')
+    resp.forms[0]['label'] = 'Test2'
+    resp.forms[0]['tiles_template_url'] = 'http://somedomain.com/blabla/{z}/{x}/{y}{r}.png'
+    resp.forms[0]['tiles_attribution'] = 'Foo bar'
+    resp.forms[0]['tiles_default'] = True
+    resp = resp.forms[0].submit()
+    assert '<li>Only one default tiles layer can be defined.</li>' in resp.text
+
+
+def test_edit_geojson_layer(app, admin_user, layer):
     app = login(app)
     resp = app.get('/manage/maps/', status=200)
     resp = resp.click('Test')
+    assert 'tiles_template_url' not in resp.context['form']
+    assert 'tiles_attribution' not in resp.context['form']
+    assert 'tiles_default' not in resp.context['form']
     assert '<li><span class="icon-ambulance"><label' in resp.text
     resp.forms[0]['geojson_url'] = 'http://example.net/new_geojson'
     resp = resp.forms[0].submit()
     assert resp.location.endswith('/manage/maps/')
-    assert MapLayer.objects.count() == 1
-    layer = MapLayer.objects.get()
+    layer.refresh_from_db()
     assert layer.geojson_url == 'http://example.net/new_geojson'
 
-def test_delete_layer(app, admin_user):
-    test_add_layer(app, admin_user)
+
+def test_edit_tiles_layer(app, admin_user, tiles_layer):
+    app = login(app)
+    resp = app.get('/manage/maps/', status=200)
+    resp = resp.click('Test')
+    assert 'cache_duration' not in resp.context['form']
+    assert 'include_user_identifier' not in resp.context['form']
+    assert 'geojson_url' not in resp.context['form']
+    assert 'marker_colour' not in resp.context['form']
+    assert 'icon' not in resp.context['form']
+    assert 'icon_colour' not in resp.context['form']
+    assert 'properties' not in resp.context['form']
+    resp.forms[0]['tiles_default'] = False
+    resp = resp.forms[0].submit()
+    assert resp.location.endswith('/manage/maps/')
+    tiles_layer.refresh_from_db()
+    assert tiles_layer.tiles_default is False
+
+
+def test_delete_layer(app, admin_user, layer):
     app = login(app)
     resp = app.get('/manage/maps/', status=200)
     resp = resp.click('remove')
@@ -68,12 +155,11 @@ def test_delete_layer(app, admin_user):
     assert MapLayer.objects.count() == 0
 
 
-def test_list_layers(app, admin_user):
+def test_list_layers(app, admin_user, layer):
     page = Page.objects.create(title='One', slug='one', template_name='standard')
     map1 = Map.objects.create(page=page, placeholder='map 1', order=0)
     map2 = Map.objects.create(page=page, placeholder='map 2', order=1)
 
-    test_add_layer(app, admin_user)
     app = login(app)
     resp = app.get('/manage/maps/', status=200)
     assert '/manage/pages/{}/#cell-{}'.format(page.pk, map1.get_reference()) in resp.text
@@ -81,9 +167,7 @@ def test_list_layers(app, admin_user):
 
 
 @mock.patch('combo.apps.maps.models.requests.get')
-def test_download_geojson(mock_request, app, admin_user):
-    test_add_layer(app, admin_user)
-    layer = MapLayer.objects.get()
+def test_download_geojson(mock_request, app, admin_user, layer):
     mocked_response = mock.Mock()
     mock_request.GET = {}
     mocked_response.json.return_value = [{'type': 'Feature',
@@ -125,18 +209,16 @@ def test_download_geojson(mock_request, app, admin_user):
         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',
-    )
+def test_add_delete_layer(app, admin_user, layer, tiles_layer):
     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/$')
+    assert list(cell.get_free_geojson_layers()) == [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]
     resp.forms[0]['map_layer'] = layer.pk
     resp = resp.forms[0].submit()
     assert resp.status_int == 302
@@ -147,8 +229,33 @@ def test_add_delete_layer(app, admin_user):
     assert options.map_layer == layer
 
     resp = resp.follow()
-    assert list(cell.get_free_layers()) == []
-    assert '/add-layer/$' 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
+    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
+
+    resp = resp.follow()
+    assert list(cell.get_free_geojson_layers()) == [layer]
+    assert list(cell.get_free_tiles_layers()) == [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 = 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 == tiles_layer
+
+    resp = resp.follow()
+    assert list(cell.get_free_geojson_layers()) == [layer]
+    assert list(cell.get_free_tiles_layers()) == []
+    assert '/add-layer/tiles/' not in resp.text
     resp = resp.click(href='.*/layer/%s/delete/$' % options.pk)
     resp = resp.forms[0].submit()
     assert resp.status_int == 302
-- 
2.20.1

