We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Translation missing for "Data explorer" in reclineview
**CKAN version=2.8.6**

**Describe the bug**
When switching to the french language, the tab name "data explorer on a resource preview page is not translated

**Steps to reproduce**
1. Installing ckan from source
2. Set up a dataset with a previewable format (e.g csv)
3. preview the data 
4. Select the french language by changing the url t0 http://XXX.X.X.X:XXXXfr/XXXXXXX


**Expected behavior**
The tab name shoud be translated to something like "Explorateur de données"


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ckanext/reclineview/plugin.py`
Content:
```
1 # encoding: utf-8
2 
3 from logging import getLogger
4 
5 import six
6 
7 from ckan.common import json, config
8 import ckan.plugins as p
9 import ckan.plugins.toolkit as toolkit
10 
11 log = getLogger(__name__)
12 ignore_empty = p.toolkit.get_validator('ignore_empty')
13 natural_number_validator = p.toolkit.get_validator('natural_number_validator')
14 Invalid = p.toolkit.Invalid
15 
16 
17 def get_mapview_config():
18     '''
19     Extracts and returns map view configuration of the reclineview extension.
20     '''
21     namespace = 'ckanext.spatial.common_map.'
22     return {k.replace(namespace, ''): v
23             for k, v in six.iteritems(config)
24             if k.startswith(namespace)}
25 
26 
27 def get_dataproxy_url():
28     '''
29     Returns the value of the ckan.recline.dataproxy_url config option
30     '''
31     return config.get(
32         'ckan.recline.dataproxy_url', '//jsonpdataproxy.appspot.com')
33 
34 
35 def in_list(list_possible_values):
36     '''
37     Validator that checks that the input value is one of the given
38     possible values.
39 
40     :param list_possible_values: function that returns list of possible values
41         for validated field
42     :type possible_values: function
43     '''
44     def validate(key, data, errors, context):
45         if not data[key] in list_possible_values():
46             raise Invalid('"{0}" is not a valid parameter'.format(data[key]))
47     return validate
48 
49 
50 def datastore_fields(resource, valid_field_types):
51     '''
52     Return a list of all datastore fields for a given resource, as long as
53     the datastore field type is in valid_field_types.
54 
55     :param resource: resource dict
56     :type resource: dict
57     :param valid_field_types: field types to include in returned list
58     :type valid_field_types: list of strings
59     '''
60     data = {'resource_id': resource['id'], 'limit': 0}
61     fields = toolkit.get_action('datastore_search')({}, data)['fields']
62     return [{'value': f['id'], 'text': f['id']} for f in fields
63             if f['type'] in valid_field_types]
64 
65 
66 class ReclineViewBase(p.SingletonPlugin):
67     '''
68     This base class for the Recline view extensions.
69     '''
70     p.implements(p.IConfigurer, inherit=True)
71     p.implements(p.IResourceView, inherit=True)
72     p.implements(p.ITemplateHelpers, inherit=True)
73 
74     def update_config(self, config):
75         '''
76         Set up the resource library, public directory and
77         template directory for the view
78         '''
79         toolkit.add_public_directory(config, 'theme/public')
80         toolkit.add_template_directory(config, 'theme/templates')
81         toolkit.add_resource('theme/public', 'ckanext-reclineview')
82 
83     def can_view(self, data_dict):
84         resource = data_dict['resource']
85         return (resource.get('datastore_active') or
86                 '_datastore_only_resource' in resource.get('url', ''))
87 
88     def setup_template_variables(self, context, data_dict):
89         return {'resource_json': json.dumps(data_dict['resource']),
90                 'resource_view_json': json.dumps(data_dict['resource_view'])}
91 
92     def view_template(self, context, data_dict):
93         return 'recline_view.html'
94 
95     def get_helpers(self):
96         return {
97             'get_map_config': get_mapview_config,
98             'get_dataproxy_url': get_dataproxy_url,
99         }
100 
101 
102 class ReclineView(ReclineViewBase):
103     '''
104     This extension views resources using a Recline MultiView.
105     '''
106 
107     def info(self):
108         return {'name': 'recline_view',
109                 'title': 'Data Explorer',
110                 'filterable': True,
111                 'icon': 'table',
112                 'requires_datastore': False,
113                 'default_title': p.toolkit._('Data Explorer'),
114                 }
115 
116     def can_view(self, data_dict):
117         resource = data_dict['resource']
118 
119         if (resource.get('datastore_active') or
120                 '_datastore_only_resource' in resource.get('url', '')):
121             return True
122         resource_format = resource.get('format', None)
123         if resource_format:
124             return resource_format.lower() in [
125                 'csv', 'xls', 'xlsx', 'ods', 'tsv'
126             ]
127         else:
128             return False
129 
130 
131 class ReclineGridView(ReclineViewBase):
132     '''
133     This extension views resources using a Recline grid.
134     '''
135 
136     def info(self):
137         return {'name': 'recline_grid_view',
138                 'title': 'Grid',
139                 'filterable': True,
140                 'icon': 'table',
141                 'requires_datastore': True,
142                 'default_title': p.toolkit._('Table'),
143                 }
144 
145 
146 class ReclineGraphView(ReclineViewBase):
147     '''
148     This extension views resources using a Recline graph.
149     '''
150 
151     graph_types = [{'value': 'lines-and-points',
152                     'text': 'Lines and points'},
153                    {'value': 'lines', 'text': 'Lines'},
154                    {'value': 'points', 'text': 'Points'},
155                    {'value': 'bars', 'text': 'Bars'},
156                    {'value': 'columns', 'text': 'Columns'}]
157 
158     datastore_fields = []
159 
160     datastore_field_types = ['numeric', 'int4', 'timestamp']
161 
162     def list_graph_types(self):
163         return [t['value'] for t in self.graph_types]
164 
165     def list_datastore_fields(self):
166         return [t['value'] for t in self.datastore_fields]
167 
168     def info(self):
169         # in_list validator here is passed functions because this
170         # method does not know what the possible values of the
171         # datastore fields are (requires a datastore search)
172         schema = {
173             'offset': [ignore_empty, natural_number_validator],
174             'limit': [ignore_empty, natural_number_validator],
175             'graph_type': [ignore_empty, in_list(self.list_graph_types)],
176             'group': [ignore_empty, in_list(self.list_datastore_fields)],
177             'series': [ignore_empty, in_list(self.list_datastore_fields)]
178         }
179         return {'name': 'recline_graph_view',
180                 'title': 'Graph',
181                 'filterable': True,
182                 'icon': 'bar-chart-o',
183                 'requires_datastore': True,
184                 'schema': schema,
185                 'default_title': p.toolkit._('Graph'),
186                 }
187 
188     def setup_template_variables(self, context, data_dict):
189         self.datastore_fields = datastore_fields(data_dict['resource'],
190                                                  self.datastore_field_types)
191         vars = ReclineViewBase.setup_template_variables(self, context,
192                                                         data_dict)
193         vars.update({'graph_types': self.graph_types,
194                      'graph_fields': self.datastore_fields})
195         return vars
196 
197     def form_template(self, context, data_dict):
198         return 'recline_graph_form.html'
199 
200 
201 class ReclineMapView(ReclineViewBase):
202     '''
203     This extension views resources using a Recline map.
204     '''
205 
206     map_field_types = [{'value': 'lat_long',
207                         'text': 'Latitude / Longitude fields'},
208                        {'value': 'geojson', 'text': 'GeoJSON'}]
209 
210     datastore_fields = []
211 
212     datastore_field_latlon_types = ['numeric']
213 
214     datastore_field_geojson_types = ['text']
215 
216     def list_map_field_types(self):
217         return [t['value'] for t in self.map_field_types]
218 
219     def list_datastore_fields(self):
220         return [t['value'] for t in self.datastore_fields]
221 
222     def info(self):
223         # in_list validator here is passed functions because this
224         # method does not know what the possible values of the
225         # datastore fields are (requires a datastore search)
226         schema = {
227             'offset': [ignore_empty, natural_number_validator],
228             'limit': [ignore_empty, natural_number_validator],
229             'map_field_type': [ignore_empty,
230                                in_list(self.list_map_field_types)],
231             'latitude_field': [ignore_empty,
232                                in_list(self.list_datastore_fields)],
233             'longitude_field': [ignore_empty,
234                                 in_list(self.list_datastore_fields)],
235             'geojson_field': [ignore_empty,
236                               in_list(self.list_datastore_fields)],
237             'auto_zoom': [ignore_empty],
238             'cluster_markers': [ignore_empty]
239         }
240         return {'name': 'recline_map_view',
241                 'title': 'Map',
242                 'schema': schema,
243                 'filterable': True,
244                 'icon': 'map-marker',
245                 'default_title': p.toolkit._('Map'),
246                 }
247 
248     def setup_template_variables(self, context, data_dict):
249         map_latlon_fields = datastore_fields(
250             data_dict['resource'], self.datastore_field_latlon_types)
251         map_geojson_fields = datastore_fields(
252             data_dict['resource'], self.datastore_field_geojson_types)
253 
254         self.datastore_fields = map_latlon_fields + map_geojson_fields
255 
256         vars = ReclineViewBase.setup_template_variables(self, context,
257                                                         data_dict)
258         vars.update({'map_field_types': self.map_field_types,
259                      'map_latlon_fields': map_latlon_fields,
260                      'map_geojson_fields': map_geojson_fields
261                      })
262         return vars
263 
264     def form_template(self, context, data_dict):
265         return 'recline_map_form.html'
266
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ckanext/reclineview/plugin.py b/ckanext/reclineview/plugin.py
--- a/ckanext/reclineview/plugin.py
+++ b/ckanext/reclineview/plugin.py
@@ -7,6 +7,7 @@
 from ckan.common import json, config
 import ckan.plugins as p
 import ckan.plugins.toolkit as toolkit
+from ckan.plugins.toolkit import _
 
 log = getLogger(__name__)
 ignore_empty = p.toolkit.get_validator('ignore_empty')
@@ -106,7 +107,7 @@
 
     def info(self):
         return {'name': 'recline_view',
-                'title': 'Data Explorer',
+                'title': _('Data Explorer'),
                 'filterable': True,
                 'icon': 'table',
                 'requires_datastore': False,
@@ -135,7 +136,7 @@
 
     def info(self):
         return {'name': 'recline_grid_view',
-                'title': 'Grid',
+                'title': _('Grid'),
                 'filterable': True,
                 'icon': 'table',
                 'requires_datastore': True,
@@ -177,7 +178,7 @@
             'series': [ignore_empty, in_list(self.list_datastore_fields)]
         }
         return {'name': 'recline_graph_view',
-                'title': 'Graph',
+                'title': _('Graph'),
                 'filterable': True,
                 'icon': 'bar-chart-o',
                 'requires_datastore': True,
@@ -238,7 +239,7 @@
             'cluster_markers': [ignore_empty]
         }
         return {'name': 'recline_map_view',
-                'title': 'Map',
+                'title': _('Map'),
                 'schema': schema,
                 'filterable': True,
                 'icon': 'map-marker',

{"golden_diff": "diff --git a/ckanext/reclineview/plugin.py b/ckanext/reclineview/plugin.py\n--- a/ckanext/reclineview/plugin.py\n+++ b/ckanext/reclineview/plugin.py\n@@ -7,6 +7,7 @@\n from ckan.common import json, config\n import ckan.plugins as p\n import ckan.plugins.toolkit as toolkit\n+from ckan.plugins.toolkit import _\n \n log = getLogger(__name__)\n ignore_empty = p.toolkit.get_validator('ignore_empty')\n@@ -106,7 +107,7 @@\n \n     def info(self):\n         return {'name': 'recline_view',\n-                'title': 'Data Explorer',\n+                'title': _('Data Explorer'),\n                 'filterable': True,\n                 'icon': 'table',\n                 'requires_datastore': False,\n@@ -135,7 +136,7 @@\n \n     def info(self):\n         return {'name': 'recline_grid_view',\n-                'title': 'Grid',\n+                'title': _('Grid'),\n                 'filterable': True,\n                 'icon': 'table',\n                 'requires_datastore': True,\n@@ -177,7 +178,7 @@\n             'series': [ignore_empty, in_list(self.list_datastore_fields)]\n         }\n         return {'name': 'recline_graph_view',\n-                'title': 'Graph',\n+                'title': _('Graph'),\n                 'filterable': True,\n                 'icon': 'bar-chart-o',\n                 'requires_datastore': True,\n@@ -238,7 +239,7 @@\n             'cluster_markers': [ignore_empty]\n         }\n         return {'name': 'recline_map_view',\n-                'title': 'Map',\n+                'title': _('Map'),\n                 'schema': schema,\n                 'filterable': True,\n                 'icon': 'map-marker',\n", "issue": "Translation missing for \"Data explorer\" in reclineview\n**CKAN version=2.8.6**\r\n\r\n**Describe the bug**\r\nWhen switching to the french language, the tab name \"data explorer on a resource preview page is not translated\r\n\r\n**Steps to reproduce**\r\n1. Installing ckan from source\r\n2. Set up a dataset with a previewable format (e.g csv)\r\n3. preview the data \r\n4. Select the french language by changing the url t0 http://XXX.X.X.X:XXXXfr/XXXXXXX\r\n\r\n\r\n**Expected behavior**\r\nThe tab name shoud be translated to something like \"Explorateur de donn\u00e9es\"\r\n\n", "before_files": [{"content": "# encoding: utf-8\n\nfrom logging import getLogger\n\nimport six\n\nfrom ckan.common import json, config\nimport ckan.plugins as p\nimport ckan.plugins.toolkit as toolkit\n\nlog = getLogger(__name__)\nignore_empty = p.toolkit.get_validator('ignore_empty')\nnatural_number_validator = p.toolkit.get_validator('natural_number_validator')\nInvalid = p.toolkit.Invalid\n\n\ndef get_mapview_config():\n    '''\n    Extracts and returns map view configuration of the reclineview extension.\n    '''\n    namespace = 'ckanext.spatial.common_map.'\n    return {k.replace(namespace, ''): v\n            for k, v in six.iteritems(config)\n            if k.startswith(namespace)}\n\n\ndef get_dataproxy_url():\n    '''\n    Returns the value of the ckan.recline.dataproxy_url config option\n    '''\n    return config.get(\n        'ckan.recline.dataproxy_url', '//jsonpdataproxy.appspot.com')\n\n\ndef in_list(list_possible_values):\n    '''\n    Validator that checks that the input value is one of the given\n    possible values.\n\n    :param list_possible_values: function that returns list of possible values\n        for validated field\n    :type possible_values: function\n    '''\n    def validate(key, data, errors, context):\n        if not data[key] in list_possible_values():\n            raise Invalid('\"{0}\" is not a valid parameter'.format(data[key]))\n    return validate\n\n\ndef datastore_fields(resource, valid_field_types):\n    '''\n    Return a list of all datastore fields for a given resource, as long as\n    the datastore field type is in valid_field_types.\n\n    :param resource: resource dict\n    :type resource: dict\n    :param valid_field_types: field types to include in returned list\n    :type valid_field_types: list of strings\n    '''\n    data = {'resource_id': resource['id'], 'limit': 0}\n    fields = toolkit.get_action('datastore_search')({}, data)['fields']\n    return [{'value': f['id'], 'text': f['id']} for f in fields\n            if f['type'] in valid_field_types]\n\n\nclass ReclineViewBase(p.SingletonPlugin):\n    '''\n    This base class for the Recline view extensions.\n    '''\n    p.implements(p.IConfigurer, inherit=True)\n    p.implements(p.IResourceView, inherit=True)\n    p.implements(p.ITemplateHelpers, inherit=True)\n\n    def update_config(self, config):\n        '''\n        Set up the resource library, public directory and\n        template directory for the view\n        '''\n        toolkit.add_public_directory(config, 'theme/public')\n        toolkit.add_template_directory(config, 'theme/templates')\n        toolkit.add_resource('theme/public', 'ckanext-reclineview')\n\n    def can_view(self, data_dict):\n        resource = data_dict['resource']\n        return (resource.get('datastore_active') or\n                '_datastore_only_resource' in resource.get('url', ''))\n\n    def setup_template_variables(self, context, data_dict):\n        return {'resource_json': json.dumps(data_dict['resource']),\n                'resource_view_json': json.dumps(data_dict['resource_view'])}\n\n    def view_template(self, context, data_dict):\n        return 'recline_view.html'\n\n    def get_helpers(self):\n        return {\n            'get_map_config': get_mapview_config,\n            'get_dataproxy_url': get_dataproxy_url,\n        }\n\n\nclass ReclineView(ReclineViewBase):\n    '''\n    This extension views resources using a Recline MultiView.\n    '''\n\n    def info(self):\n        return {'name': 'recline_view',\n                'title': 'Data Explorer',\n                'filterable': True,\n                'icon': 'table',\n                'requires_datastore': False,\n                'default_title': p.toolkit._('Data Explorer'),\n                }\n\n    def can_view(self, data_dict):\n        resource = data_dict['resource']\n\n        if (resource.get('datastore_active') or\n                '_datastore_only_resource' in resource.get('url', '')):\n            return True\n        resource_format = resource.get('format', None)\n        if resource_format:\n            return resource_format.lower() in [\n                'csv', 'xls', 'xlsx', 'ods', 'tsv'\n            ]\n        else:\n            return False\n\n\nclass ReclineGridView(ReclineViewBase):\n    '''\n    This extension views resources using a Recline grid.\n    '''\n\n    def info(self):\n        return {'name': 'recline_grid_view',\n                'title': 'Grid',\n                'filterable': True,\n                'icon': 'table',\n                'requires_datastore': True,\n                'default_title': p.toolkit._('Table'),\n                }\n\n\nclass ReclineGraphView(ReclineViewBase):\n    '''\n    This extension views resources using a Recline graph.\n    '''\n\n    graph_types = [{'value': 'lines-and-points',\n                    'text': 'Lines and points'},\n                   {'value': 'lines', 'text': 'Lines'},\n                   {'value': 'points', 'text': 'Points'},\n                   {'value': 'bars', 'text': 'Bars'},\n                   {'value': 'columns', 'text': 'Columns'}]\n\n    datastore_fields = []\n\n    datastore_field_types = ['numeric', 'int4', 'timestamp']\n\n    def list_graph_types(self):\n        return [t['value'] for t in self.graph_types]\n\n    def list_datastore_fields(self):\n        return [t['value'] for t in self.datastore_fields]\n\n    def info(self):\n        # in_list validator here is passed functions because this\n        # method does not know what the possible values of the\n        # datastore fields are (requires a datastore search)\n        schema = {\n            'offset': [ignore_empty, natural_number_validator],\n            'limit': [ignore_empty, natural_number_validator],\n            'graph_type': [ignore_empty, in_list(self.list_graph_types)],\n            'group': [ignore_empty, in_list(self.list_datastore_fields)],\n            'series': [ignore_empty, in_list(self.list_datastore_fields)]\n        }\n        return {'name': 'recline_graph_view',\n                'title': 'Graph',\n                'filterable': True,\n                'icon': 'bar-chart-o',\n                'requires_datastore': True,\n                'schema': schema,\n                'default_title': p.toolkit._('Graph'),\n                }\n\n    def setup_template_variables(self, context, data_dict):\n        self.datastore_fields = datastore_fields(data_dict['resource'],\n                                                 self.datastore_field_types)\n        vars = ReclineViewBase.setup_template_variables(self, context,\n                                                        data_dict)\n        vars.update({'graph_types': self.graph_types,\n                     'graph_fields': self.datastore_fields})\n        return vars\n\n    def form_template(self, context, data_dict):\n        return 'recline_graph_form.html'\n\n\nclass ReclineMapView(ReclineViewBase):\n    '''\n    This extension views resources using a Recline map.\n    '''\n\n    map_field_types = [{'value': 'lat_long',\n                        'text': 'Latitude / Longitude fields'},\n                       {'value': 'geojson', 'text': 'GeoJSON'}]\n\n    datastore_fields = []\n\n    datastore_field_latlon_types = ['numeric']\n\n    datastore_field_geojson_types = ['text']\n\n    def list_map_field_types(self):\n        return [t['value'] for t in self.map_field_types]\n\n    def list_datastore_fields(self):\n        return [t['value'] for t in self.datastore_fields]\n\n    def info(self):\n        # in_list validator here is passed functions because this\n        # method does not know what the possible values of the\n        # datastore fields are (requires a datastore search)\n        schema = {\n            'offset': [ignore_empty, natural_number_validator],\n            'limit': [ignore_empty, natural_number_validator],\n            'map_field_type': [ignore_empty,\n                               in_list(self.list_map_field_types)],\n            'latitude_field': [ignore_empty,\n                               in_list(self.list_datastore_fields)],\n            'longitude_field': [ignore_empty,\n                                in_list(self.list_datastore_fields)],\n            'geojson_field': [ignore_empty,\n                              in_list(self.list_datastore_fields)],\n            'auto_zoom': [ignore_empty],\n            'cluster_markers': [ignore_empty]\n        }\n        return {'name': 'recline_map_view',\n                'title': 'Map',\n                'schema': schema,\n                'filterable': True,\n                'icon': 'map-marker',\n                'default_title': p.toolkit._('Map'),\n                }\n\n    def setup_template_variables(self, context, data_dict):\n        map_latlon_fields = datastore_fields(\n            data_dict['resource'], self.datastore_field_latlon_types)\n        map_geojson_fields = datastore_fields(\n            data_dict['resource'], self.datastore_field_geojson_types)\n\n        self.datastore_fields = map_latlon_fields + map_geojson_fields\n\n        vars = ReclineViewBase.setup_template_variables(self, context,\n                                                        data_dict)\n        vars.update({'map_field_types': self.map_field_types,\n                     'map_latlon_fields': map_latlon_fields,\n                     'map_geojson_fields': map_geojson_fields\n                     })\n        return vars\n\n    def form_template(self, context, data_dict):\n        return 'recline_map_form.html'\n", "path": "ckanext/reclineview/plugin.py"}], "after_files": [{"content": "# encoding: utf-8\n\nfrom logging import getLogger\n\nimport six\n\nfrom ckan.common import json, config\nimport ckan.plugins as p\nimport ckan.plugins.toolkit as toolkit\nfrom ckan.plugins.toolkit import _\n\nlog = getLogger(__name__)\nignore_empty = p.toolkit.get_validator('ignore_empty')\nnatural_number_validator = p.toolkit.get_validator('natural_number_validator')\nInvalid = p.toolkit.Invalid\n\n\ndef get_mapview_config():\n    '''\n    Extracts and returns map view configuration of the reclineview extension.\n    '''\n    namespace = 'ckanext.spatial.common_map.'\n    return {k.replace(namespace, ''): v\n            for k, v in six.iteritems(config)\n            if k.startswith(namespace)}\n\n\ndef get_dataproxy_url():\n    '''\n    Returns the value of the ckan.recline.dataproxy_url config option\n    '''\n    return config.get(\n        'ckan.recline.dataproxy_url', '//jsonpdataproxy.appspot.com')\n\n\ndef in_list(list_possible_values):\n    '''\n    Validator that checks that the input value is one of the given\n    possible values.\n\n    :param list_possible_values: function that returns list of possible values\n        for validated field\n    :type possible_values: function\n    '''\n    def validate(key, data, errors, context):\n        if not data[key] in list_possible_values():\n            raise Invalid('\"{0}\" is not a valid parameter'.format(data[key]))\n    return validate\n\n\ndef datastore_fields(resource, valid_field_types):\n    '''\n    Return a list of all datastore fields for a given resource, as long as\n    the datastore field type is in valid_field_types.\n\n    :param resource: resource dict\n    :type resource: dict\n    :param valid_field_types: field types to include in returned list\n    :type valid_field_types: list of strings\n    '''\n    data = {'resource_id': resource['id'], 'limit': 0}\n    fields = toolkit.get_action('datastore_search')({}, data)['fields']\n    return [{'value': f['id'], 'text': f['id']} for f in fields\n            if f['type'] in valid_field_types]\n\n\nclass ReclineViewBase(p.SingletonPlugin):\n    '''\n    This base class for the Recline view extensions.\n    '''\n    p.implements(p.IConfigurer, inherit=True)\n    p.implements(p.IResourceView, inherit=True)\n    p.implements(p.ITemplateHelpers, inherit=True)\n\n    def update_config(self, config):\n        '''\n        Set up the resource library, public directory and\n        template directory for the view\n        '''\n        toolkit.add_public_directory(config, 'theme/public')\n        toolkit.add_template_directory(config, 'theme/templates')\n        toolkit.add_resource('theme/public', 'ckanext-reclineview')\n\n    def can_view(self, data_dict):\n        resource = data_dict['resource']\n        return (resource.get('datastore_active') or\n                '_datastore_only_resource' in resource.get('url', ''))\n\n    def setup_template_variables(self, context, data_dict):\n        return {'resource_json': json.dumps(data_dict['resource']),\n                'resource_view_json': json.dumps(data_dict['resource_view'])}\n\n    def view_template(self, context, data_dict):\n        return 'recline_view.html'\n\n    def get_helpers(self):\n        return {\n            'get_map_config': get_mapview_config,\n            'get_dataproxy_url': get_dataproxy_url,\n        }\n\n\nclass ReclineView(ReclineViewBase):\n    '''\n    This extension views resources using a Recline MultiView.\n    '''\n\n    def info(self):\n        return {'name': 'recline_view',\n                'title': _('Data Explorer'),\n                'filterable': True,\n                'icon': 'table',\n                'requires_datastore': False,\n                'default_title': p.toolkit._('Data Explorer'),\n                }\n\n    def can_view(self, data_dict):\n        resource = data_dict['resource']\n\n        if (resource.get('datastore_active') or\n                '_datastore_only_resource' in resource.get('url', '')):\n            return True\n        resource_format = resource.get('format', None)\n        if resource_format:\n            return resource_format.lower() in [\n                'csv', 'xls', 'xlsx', 'ods', 'tsv'\n            ]\n        else:\n            return False\n\n\nclass ReclineGridView(ReclineViewBase):\n    '''\n    This extension views resources using a Recline grid.\n    '''\n\n    def info(self):\n        return {'name': 'recline_grid_view',\n                'title': _('Grid'),\n                'filterable': True,\n                'icon': 'table',\n                'requires_datastore': True,\n                'default_title': p.toolkit._('Table'),\n                }\n\n\nclass ReclineGraphView(ReclineViewBase):\n    '''\n    This extension views resources using a Recline graph.\n    '''\n\n    graph_types = [{'value': 'lines-and-points',\n                    'text': 'Lines and points'},\n                   {'value': 'lines', 'text': 'Lines'},\n                   {'value': 'points', 'text': 'Points'},\n                   {'value': 'bars', 'text': 'Bars'},\n                   {'value': 'columns', 'text': 'Columns'}]\n\n    datastore_fields = []\n\n    datastore_field_types = ['numeric', 'int4', 'timestamp']\n\n    def list_graph_types(self):\n        return [t['value'] for t in self.graph_types]\n\n    def list_datastore_fields(self):\n        return [t['value'] for t in self.datastore_fields]\n\n    def info(self):\n        # in_list validator here is passed functions because this\n        # method does not know what the possible values of the\n        # datastore fields are (requires a datastore search)\n        schema = {\n            'offset': [ignore_empty, natural_number_validator],\n            'limit': [ignore_empty, natural_number_validator],\n            'graph_type': [ignore_empty, in_list(self.list_graph_types)],\n            'group': [ignore_empty, in_list(self.list_datastore_fields)],\n            'series': [ignore_empty, in_list(self.list_datastore_fields)]\n        }\n        return {'name': 'recline_graph_view',\n                'title': _('Graph'),\n                'filterable': True,\n                'icon': 'bar-chart-o',\n                'requires_datastore': True,\n                'schema': schema,\n                'default_title': p.toolkit._('Graph'),\n                }\n\n    def setup_template_variables(self, context, data_dict):\n        self.datastore_fields = datastore_fields(data_dict['resource'],\n                                                 self.datastore_field_types)\n        vars = ReclineViewBase.setup_template_variables(self, context,\n                                                        data_dict)\n        vars.update({'graph_types': self.graph_types,\n                     'graph_fields': self.datastore_fields})\n        return vars\n\n    def form_template(self, context, data_dict):\n        return 'recline_graph_form.html'\n\n\nclass ReclineMapView(ReclineViewBase):\n    '''\n    This extension views resources using a Recline map.\n    '''\n\n    map_field_types = [{'value': 'lat_long',\n                        'text': 'Latitude / Longitude fields'},\n                       {'value': 'geojson', 'text': 'GeoJSON'}]\n\n    datastore_fields = []\n\n    datastore_field_latlon_types = ['numeric']\n\n    datastore_field_geojson_types = ['text']\n\n    def list_map_field_types(self):\n        return [t['value'] for t in self.map_field_types]\n\n    def list_datastore_fields(self):\n        return [t['value'] for t in self.datastore_fields]\n\n    def info(self):\n        # in_list validator here is passed functions because this\n        # method does not know what the possible values of the\n        # datastore fields are (requires a datastore search)\n        schema = {\n            'offset': [ignore_empty, natural_number_validator],\n            'limit': [ignore_empty, natural_number_validator],\n            'map_field_type': [ignore_empty,\n                               in_list(self.list_map_field_types)],\n            'latitude_field': [ignore_empty,\n                               in_list(self.list_datastore_fields)],\n            'longitude_field': [ignore_empty,\n                                in_list(self.list_datastore_fields)],\n            'geojson_field': [ignore_empty,\n                              in_list(self.list_datastore_fields)],\n            'auto_zoom': [ignore_empty],\n            'cluster_markers': [ignore_empty]\n        }\n        return {'name': 'recline_map_view',\n                'title': _('Map'),\n                'schema': schema,\n                'filterable': True,\n                'icon': 'map-marker',\n                'default_title': p.toolkit._('Map'),\n                }\n\n    def setup_template_variables(self, context, data_dict):\n        map_latlon_fields = datastore_fields(\n            data_dict['resource'], self.datastore_field_latlon_types)\n        map_geojson_fields = datastore_fields(\n            data_dict['resource'], self.datastore_field_geojson_types)\n\n        self.datastore_fields = map_latlon_fields + map_geojson_fields\n\n        vars = ReclineViewBase.setup_template_variables(self, context,\n                                                        data_dict)\n        vars.update({'map_field_types': self.map_field_types,\n                     'map_latlon_fields': map_latlon_fields,\n                     'map_geojson_fields': map_geojson_fields\n                     })\n        return vars\n\n    def form_template(self, context, data_dict):\n        return 'recline_map_form.html'\n", "path": "ckanext/reclineview/plugin.py"}]}