From 46bf55023bb1893b68f6c26da8b89a1899ecce74 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Mon, 9 Nov 2020 16:55:44 +0100
Subject: [PATCH 2/3] backoffice: add options to item field for plotting
 choices on a map (#47066)

---
 tests/admin_pages/test_form.py       | 40 +++++++++++++
 wcs/data_sources.py                  |  2 +-
 wcs/fields.py                        | 90 ++++++++++++++++------------
 wcs/qommon/static/js/qommon.admin.js | 12 ++++
 4 files changed, 106 insertions(+), 38 deletions(-)

diff --git a/tests/admin_pages/test_form.py b/tests/admin_pages/test_form.py
index d9535f84e..bc25efa21 100644
--- a/tests/admin_pages/test_form.py
+++ b/tests/admin_pages/test_form.py
@@ -1569,6 +1569,46 @@ def test_form_edit_item_field_data_source(pub):
     resp = app.get('/backoffice/forms/1/')
 
 
+def test_form_edit_item_field_geojson_data_source(pub, http_requests):
+    NamedDataSource.wipe()
+    create_superuser(pub)
+    create_role()
+
+    NamedDataSource.wipe()
+    data_source = NamedDataSource(name='foobar')
+    data_source.data_source = {
+        'type': 'geojson',
+        'value': 'http://remote.example.net/geojson',
+    }
+    data_source.id_property = 'id'
+    data_source.label_template_property = '{{ text }}'
+    data_source.cache_duration = '5'
+    data_source.store()
+
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [fields.ItemField(id='1', label='1st field', type='item')]
+    formdef.store()
+
+    app = login(get_app(pub))
+
+    resp = app.get('/backoffice/forms/1/fields/1/')
+    resp.form['display_mode'] = 'map'
+    assert resp.pyquery('option[value=foobar][data-type=geojson]')
+    resp.form['data_mode'] = 'data-source'
+    resp.form['data_source$type'] = 'foobar'
+    resp.form['min_zoom'] = 'Wide area'
+    resp.form['max_zoom'] = 'Small road'
+    resp = resp.form.submit('submit').follow()
+    formdef = FormDef.get(formdef.id)
+    assert formdef.fields[0].data_source == {'type': 'foobar'}
+    assert formdef.fields[0].min_zoom == '9'
+
+    resp = app.get('/backoffice/forms/1/fields/1/')
+    assert resp.form['min_zoom'].value == 'Wide area'
+
+
 def test_form_edit_items_field(pub):
     create_superuser(pub)
     create_role()
diff --git a/wcs/data_sources.py b/wcs/data_sources.py
index d31dfeff7..679f2a5b4 100644
--- a/wcs/data_sources.py
+++ b/wcs/data_sources.py
@@ -68,7 +68,7 @@ class DataSourceSelectionWidget(CompositeWidget):
                 options.append(OptGroup(_('Cards')))
                 options.extend(cards_options)
 
-            nds_options = [(x.slug, x.name, x.slug) for x in NamedDataSource.select()]
+            nds_options = [(x.slug, x.name, x.slug, {'data-type': x.type}) for x in NamedDataSource.select()]
             nds_options.sort(key=lambda x: misc.simplify(x[1]))
             if nds_options:
                 options.append(OptGroup(_('Manually Configured Data Sources')))
diff --git a/wcs/fields.py b/wcs/fields.py
index 7728caed9..22fe06beb 100644
--- a/wcs/fields.py
+++ b/wcs/fields.py
@@ -1428,7 +1428,44 @@ def item_items_stats(field, values):
     return r.getvalue()
 
 
-class ItemField(WidgetField):
+class MapOptionsMixin:
+    initial_zoom = None
+    min_zoom = None
+    max_zoom = None
+
+    def fill_zoom_admin_form(self, form, **kwargs):
+        # 0: whole world, 9: wide area, 11: area, 13: town, 16: small road
+        zoom_levels = [(None, '---'),
+                ('0', _('Whole world')),
+                ('6', _('Country')),
+                ('9', _('Wide area')),
+                ('11', _('Area')),
+                ('13', _('Town')),
+                ('16', _('Small road')),
+                ('18', _('Neighbourhood')),
+                ('19', _('Ant')),]
+        form.add(SingleSelectWidget, 'initial_zoom', title=_('Initial zoom level'),
+                value=self.initial_zoom or '13', options=zoom_levels, **kwargs)
+        form.add(SingleSelectWidget, 'min_zoom', title=_('Minimal zoom level'),
+                value=self.min_zoom, options=zoom_levels, required=False, **kwargs)
+        form.add(SingleSelectWidget, 'max_zoom', title=_('Maximal zoom level'),
+                value=self.max_zoom, options=zoom_levels, required=False, **kwargs)
+
+    def check_zoom_admin_form(self, form):
+        initial_zoom = form.get_widget('initial_zoom').parse()
+        min_zoom = form.get_widget('min_zoom').parse()
+        max_zoom = form.get_widget('max_zoom').parse()
+        if min_zoom and max_zoom:
+            if int(min_zoom) > int(max_zoom):
+                form.get_widget('min_zoom').set_error(
+                        _('Minimal zoom level cannot be greater than maximal zoom level.'))
+        if (initial_zoom and min_zoom and int(initial_zoom) < int(min_zoom)) or (
+                (initial_zoom and max_zoom and int(initial_zoom) > int(max_zoom))):
+            form.get_widget('initial_zoom').set_error(
+                        _('Initial zoom level must be between minimal and maximal zoom levels.'))
+
+
+class ItemField(WidgetField, MapOptionsMixin):
     key = 'item'
     description = N_('List')
 
@@ -1628,14 +1665,16 @@ class ItemField(WidgetField):
         WidgetField.fill_admin_form(self, form)
         form.add(CheckboxWidget, 'in_filters', title=_('Display in default filters'),
                 value=self.in_filters, advanced=True)
-        options = [('list', _('List')),
-                   ('radio', _('Radio buttons')),
-                   ('autocomplete', _('Autocomplete')),
+        options = [('list', _('List'), 'list'),
+                   ('radio', _('Radio buttons'), 'radio'),
+                   ('autocomplete', _('Autocomplete'), 'autocomplete'),
+                   ('map', _('Map (requires geographical data)'), 'map'),
                   ]
         form.add(RadiobuttonsWidget, 'display_mode',
                 title=_('Display Mode'),
                 options=options,
-                value=self.display_mode)
+                value=self.display_mode,
+                attrs={'data-dynamic-display-parent': 'true'})
         real_data_source = data_sources.get_real(self.data_source)
         form.add(RadiobuttonsWidget, 'data_mode',
                 title=_('Data'),
@@ -1663,11 +1702,14 @@ class ItemField(WidgetField):
                 title=_('Display disabled items'),
                 value=self.display_disabled_items,
                 advanced=not(self.display_disabled_items))
+        self.fill_zoom_admin_form(form,
+                attrs={'data-dynamic-display-child-of': 'display_mode',
+                       'data-dynamic-display-value': 'map'})
 
     def get_admin_attributes(self):
         return WidgetField.get_admin_attributes(self) + ['items',
                 'display_mode', 'data_source', 'in_filters', 'anonymise',
-                'display_disabled_items']
+                'display_disabled_items', 'initial_zoom', 'min_zoom', 'max_zoom']
 
     def check_admin_form(self, form):
         data_mode = form.get_widget('data_mode').parse()
@@ -1684,6 +1726,8 @@ class ItemField(WidgetField):
             data_source_type.set_value(None)
             data_source_type.transfer_form_value(get_request())
 
+        self.check_zoom_admin_form(form)
+
     def stats(self, values):
         return item_items_stats(self, values)
 
@@ -2396,13 +2440,10 @@ class TableRowsField(WidgetField):
 register_field_class(TableRowsField)
 
 
-class MapField(WidgetField):
+class MapField(WidgetField, MapOptionsMixin):
     key = 'map'
     description = N_('Map')
 
-    initial_zoom = None
-    min_zoom = None
-    max_zoom = None
     default_position = None
     init_with_geoloc = False
 
@@ -2412,22 +2453,7 @@ class MapField(WidgetField):
 
     def fill_admin_form(self, form):
         WidgetField.fill_admin_form(self, form)
-        # 0: whole world, 9: wide area, 11: area, 13: town, 16: small road
-        zoom_levels = [(None, '---'),
-                ('0', _('Whole world')),
-                ('6', _('Country')),
-                ('9', _('Wide area')),
-                ('11', _('Area')),
-                ('13', _('Town')),
-                ('16', _('Small road')),
-                ('18', _('Neighbourhood')),
-                ('19', _('Ant')),]
-        form.add(SingleSelectWidget, 'initial_zoom', title=_('Initial zoom level'),
-                value=self.initial_zoom or '13', options=zoom_levels)
-        form.add(SingleSelectWidget, 'min_zoom', title=_('Minimal zoom level'),
-                value=self.min_zoom, options=zoom_levels, required=False)
-        form.add(SingleSelectWidget, 'max_zoom', title=_('Maximal zoom level'),
-                value=self.max_zoom, options=zoom_levels, required=False)
+        self.fill_zoom_admin_form(form)
         form.add(MapWidget, 'default_position', title=_('Initial Position'),
                 value=self.default_position, default_zoom='9', required=False)
         form.add(CheckboxWidget, 'init_with_geoloc',
@@ -2435,17 +2461,7 @@ class MapField(WidgetField):
                 value=self.init_with_geoloc, required=False)
 
     def check_admin_form(self, form):
-        initial_zoom = form.get_widget('initial_zoom').parse()
-        min_zoom = form.get_widget('min_zoom').parse()
-        max_zoom = form.get_widget('max_zoom').parse()
-        if min_zoom and max_zoom:
-            if int(min_zoom) > int(max_zoom):
-                form.get_widget('min_zoom').set_error(
-                        _('Minimal zoom level cannot be greater than maximal zoom level.'))
-        if (initial_zoom and min_zoom and int(initial_zoom) < int(min_zoom)) or (
-                (initial_zoom and max_zoom and int(initial_zoom) > int(max_zoom))):
-            form.get_widget('initial_zoom').set_error(
-                        _('Initial zoom level must be between minimal and maximal zoom levels.'))
+        self.check_zoom_admin_form(form)
 
     def get_admin_attributes(self):
         return WidgetField.get_admin_attributes(self) + ['initial_zoom',
diff --git a/wcs/qommon/static/js/qommon.admin.js b/wcs/qommon/static/js/qommon.admin.js
index 1e0cdb17e..0c45d6894 100644
--- a/wcs/qommon/static/js/qommon.admin.js
+++ b/wcs/qommon/static/js/qommon.admin.js
@@ -254,4 +254,16 @@ $(function() {
             panned_svg.resize();
         });
     }
+
+    $('[type=radio][name=display_mode]').on('change', function() {
+      if ($(this).val() == 'map') {
+        $('input[name="data_mode"][value="data-source"]').click()
+        $('select[name="data_source$type"] option:not([data-type="geojson"])').hide();
+        if ($('select[name="data_source$type"] option:selected:visible').length == 0) {
+          $('select[name="data_source$type"] option:visible').first().prop('selected', true);
+        }
+      } else {
+        $('select[name="data_source$type"] option').show();
+      }
+    });
 });
-- 
2.29.2

