prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Download options in Datatables_view do not work
  **CKAN version**
  2.9
  
  **Describe the bug**
  Using datatables_view as a default resource view, which works well.  Apart from the nicer UI and pagination, one benefit of the view is that you can download a filtered version of the resource (https://github.com/ckan/ckan/pull/4497).  However, none of the datatables_view download buttons work to download the filtered data.
  
  **Steps to reproduce**
  
  1. Add a CSV resource to a dataset
  2. Create a datatables resource view (labeled 'Table' in the resource view picklist)
  3. Go to resource view and try to use the Download button for any format type
  4. A 404 error page replaces the datatables control
  
  
  
  --- 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/datatablesview/blueprint.py`
  Content:
  ```
  1 # encoding: utf-8
  2 
  3 from six.moves.urllib.parse import urlencode
  4 
  5 from flask import Blueprint
  6 from six import text_type
  7 
  8 from ckan.common import json
  9 from ckan.plugins.toolkit import get_action, request, h
  10 
  11 datatablesview = Blueprint(u'datatablesview', __name__)
  12 
  13 
  14 def merge_filters(view_filters, user_filters_str):
  15     u'''
  16     view filters are built as part of the view, user filters
  17     are selected by the user interacting with the view. Any filters
  18     selected by user may only tighten filters set in the view,
  19     others are ignored.
  20 
  21     >>> merge_filters({
  22     ...    u'Department': [u'BTDT'], u'OnTime_Status': [u'ONTIME']},
  23     ...    u'CASE_STATUS:Open|CASE_STATUS:Closed|Department:INFO')
  24     {u'Department': [u'BTDT'],
  25      u'OnTime_Status': [u'ONTIME'],
  26      u'CASE_STATUS': [u'Open', u'Closed']}
  27     '''
  28     filters = dict(view_filters)
  29     if not user_filters_str:
  30         return filters
  31     user_filters = {}
  32     for k_v in user_filters_str.split(u'|'):
  33         k, sep, v = k_v.partition(u':')
  34         if k not in view_filters or v in view_filters[k]:
  35             user_filters.setdefault(k, []).append(v)
  36     for k in user_filters:
  37         filters[k] = user_filters[k]
  38     return filters
  39 
  40 
  41 def ajax(resource_view_id):
  42     resource_view = get_action(u'resource_view_show'
  43                                )(None, {
  44                                    u'id': resource_view_id
  45                                })
  46 
  47     draw = int(request.form[u'draw'])
  48     search_text = text_type(request.form[u'search[value]'])
  49     offset = int(request.form[u'start'])
  50     limit = int(request.form[u'length'])
  51     view_filters = resource_view.get(u'filters', {})
  52     user_filters = text_type(request.form[u'filters'])
  53     filters = merge_filters(view_filters, user_filters)
  54 
  55     datastore_search = get_action(u'datastore_search')
  56     unfiltered_response = datastore_search(
  57         None, {
  58             u"resource_id": resource_view[u'resource_id'],
  59             u"limit": 0,
  60             u"filters": view_filters,
  61         }
  62     )
  63 
  64     cols = [f[u'id'] for f in unfiltered_response[u'fields']]
  65     if u'show_fields' in resource_view:
  66         cols = [c for c in cols if c in resource_view[u'show_fields']]
  67 
  68     sort_list = []
  69     i = 0
  70     while True:
  71         if u'order[%d][column]' % i not in request.form:
  72             break
  73         sort_by_num = int(request.form[u'order[%d][column]' % i])
  74         sort_order = (
  75             u'desc' if request.form[u'order[%d][dir]' %
  76                                     i] == u'desc' else u'asc'
  77         )
  78         sort_list.append(cols[sort_by_num] + u' ' + sort_order)
  79         i += 1
  80 
  81     response = datastore_search(
  82         None, {
  83             u"q": search_text,
  84             u"resource_id": resource_view[u'resource_id'],
  85             u"offset": offset,
  86             u"limit": limit,
  87             u"sort": u', '.join(sort_list),
  88             u"filters": filters,
  89         }
  90     )
  91 
  92     return json.dumps({
  93         u'draw': draw,
  94         u'iTotalRecords': unfiltered_response.get(u'total', 0),
  95         u'iTotalDisplayRecords': response.get(u'total', 0),
  96         u'aaData': [[text_type(row.get(colname, u''))
  97                      for colname in cols]
  98                     for row in response[u'records']],
  99     })
  100 
  101 
  102 def filtered_download(resource_view_id):
  103     params = json.loads(request.params[u'params'])
  104     resource_view = get_action(u'resource_view_show'
  105                                )(None, {
  106                                    u'id': resource_view_id
  107                                })
  108 
  109     search_text = text_type(params[u'search'][u'value'])
  110     view_filters = resource_view.get(u'filters', {})
  111     user_filters = text_type(params[u'filters'])
  112     filters = merge_filters(view_filters, user_filters)
  113 
  114     datastore_search = get_action(u'datastore_search')
  115     unfiltered_response = datastore_search(
  116         None, {
  117             u"resource_id": resource_view[u'resource_id'],
  118             u"limit": 0,
  119             u"filters": view_filters,
  120         }
  121     )
  122 
  123     cols = [f[u'id'] for f in unfiltered_response[u'fields']]
  124     if u'show_fields' in resource_view:
  125         cols = [c for c in cols if c in resource_view[u'show_fields']]
  126 
  127     sort_list = []
  128     for order in params[u'order']:
  129         sort_by_num = int(order[u'column'])
  130         sort_order = (u'desc' if order[u'dir'] == u'desc' else u'asc')
  131         sort_list.append(cols[sort_by_num] + u' ' + sort_order)
  132 
  133     cols = [c for (c, v) in zip(cols, params[u'visible']) if v]
  134 
  135     h.redirect_to(
  136         h.
  137         url_for(u'datastore.dump', resource_id=resource_view[u'resource_id']) +
  138         u'?' + urlencode({
  139             u'q': search_text,
  140             u'sort': u','.join(sort_list),
  141             u'filters': json.dumps(filters),
  142             u'format': request.params[u'format'],
  143             u'fields': u','.join(cols),
  144         })
  145     )
  146 
  147 
  148 datatablesview.add_url_rule(
  149     u'/datatables/ajax/<resource_view_id>', view_func=ajax, methods=[u'POST']
  150 )
  151 
  152 datatablesview.add_url_rule(
  153     u'/datatables/filtered-download/<resource_view_id>',
  154     view_func=filtered_download
  155 )
  156
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/ckanext/datatablesview/blueprint.py b/ckanext/datatablesview/blueprint.py
  --- a/ckanext/datatablesview/blueprint.py
  +++ b/ckanext/datatablesview/blueprint.py
  @@ -100,7 +100,7 @@
   
   
   def filtered_download(resource_view_id):
  -    params = json.loads(request.params[u'params'])
  +    params = json.loads(request.form[u'params'])
       resource_view = get_action(u'resource_view_show'
                                  )(None, {
                                      u'id': resource_view_id
  @@ -132,14 +132,14 @@
   
       cols = [c for (c, v) in zip(cols, params[u'visible']) if v]
   
  -    h.redirect_to(
  +    return h.redirect_to(
           h.
           url_for(u'datastore.dump', resource_id=resource_view[u'resource_id']) +
           u'?' + urlencode({
               u'q': search_text,
               u'sort': u','.join(sort_list),
               u'filters': json.dumps(filters),
  -            u'format': request.params[u'format'],
  +            u'format': request.form[u'format'],
               u'fields': u','.join(cols),
           })
       )
  @@ -151,5 +151,5 @@
   
   datatablesview.add_url_rule(
       u'/datatables/filtered-download/<resource_view_id>',
  -    view_func=filtered_download
  +    view_func=filtered_download, methods=[u'POST']
   )
  
verification_info: {"golden_diff": "diff --git a/ckanext/datatablesview/blueprint.py b/ckanext/datatablesview/blueprint.py\n--- a/ckanext/datatablesview/blueprint.py\n+++ b/ckanext/datatablesview/blueprint.py\n@@ -100,7 +100,7 @@\n \n \n def filtered_download(resource_view_id):\n-    params = json.loads(request.params[u'params'])\n+    params = json.loads(request.form[u'params'])\n     resource_view = get_action(u'resource_view_show'\n                                )(None, {\n                                    u'id': resource_view_id\n@@ -132,14 +132,14 @@\n \n     cols = [c for (c, v) in zip(cols, params[u'visible']) if v]\n \n-    h.redirect_to(\n+    return h.redirect_to(\n         h.\n         url_for(u'datastore.dump', resource_id=resource_view[u'resource_id']) +\n         u'?' + urlencode({\n             u'q': search_text,\n             u'sort': u','.join(sort_list),\n             u'filters': json.dumps(filters),\n-            u'format': request.params[u'format'],\n+            u'format': request.form[u'format'],\n             u'fields': u','.join(cols),\n         })\n     )\n@@ -151,5 +151,5 @@\n \n datatablesview.add_url_rule(\n     u'/datatables/filtered-download/<resource_view_id>',\n-    view_func=filtered_download\n+    view_func=filtered_download, methods=[u'POST']\n )\n", "issue": "Download options in Datatables_view do not work\n**CKAN version**\r\n2.9\r\n\r\n**Describe the bug**\r\nUsing datatables_view as a default resource view, which works well.  Apart from the nicer UI and pagination, one benefit of the view is that you can download a filtered version of the resource (https://github.com/ckan/ckan/pull/4497).  However, none of the datatables_view download buttons work to download the filtered data.\r\n\r\n**Steps to reproduce**\r\n\r\n1. Add a CSV resource to a dataset\r\n2. Create a datatables resource view (labeled 'Table' in the resource view picklist)\r\n3. Go to resource view and try to use the Download button for any format type\r\n4. A 404 error page replaces the datatables control\r\n\r\n\n", "before_files": [{"content": "# encoding: utf-8\n\nfrom six.moves.urllib.parse import urlencode\n\nfrom flask import Blueprint\nfrom six import text_type\n\nfrom ckan.common import json\nfrom ckan.plugins.toolkit import get_action, request, h\n\ndatatablesview = Blueprint(u'datatablesview', __name__)\n\n\ndef merge_filters(view_filters, user_filters_str):\n    u'''\n    view filters are built as part of the view, user filters\n    are selected by the user interacting with the view. Any filters\n    selected by user may only tighten filters set in the view,\n    others are ignored.\n\n    >>> merge_filters({\n    ...    u'Department': [u'BTDT'], u'OnTime_Status': [u'ONTIME']},\n    ...    u'CASE_STATUS:Open|CASE_STATUS:Closed|Department:INFO')\n    {u'Department': [u'BTDT'],\n     u'OnTime_Status': [u'ONTIME'],\n     u'CASE_STATUS': [u'Open', u'Closed']}\n    '''\n    filters = dict(view_filters)\n    if not user_filters_str:\n        return filters\n    user_filters = {}\n    for k_v in user_filters_str.split(u'|'):\n        k, sep, v = k_v.partition(u':')\n        if k not in view_filters or v in view_filters[k]:\n            user_filters.setdefault(k, []).append(v)\n    for k in user_filters:\n        filters[k] = user_filters[k]\n    return filters\n\n\ndef ajax(resource_view_id):\n    resource_view = get_action(u'resource_view_show'\n                               )(None, {\n                                   u'id': resource_view_id\n                               })\n\n    draw = int(request.form[u'draw'])\n    search_text = text_type(request.form[u'search[value]'])\n    offset = int(request.form[u'start'])\n    limit = int(request.form[u'length'])\n    view_filters = resource_view.get(u'filters', {})\n    user_filters = text_type(request.form[u'filters'])\n    filters = merge_filters(view_filters, user_filters)\n\n    datastore_search = get_action(u'datastore_search')\n    unfiltered_response = datastore_search(\n        None, {\n            u\"resource_id\": resource_view[u'resource_id'],\n            u\"limit\": 0,\n            u\"filters\": view_filters,\n        }\n    )\n\n    cols = [f[u'id'] for f in unfiltered_response[u'fields']]\n    if u'show_fields' in resource_view:\n        cols = [c for c in cols if c in resource_view[u'show_fields']]\n\n    sort_list = []\n    i = 0\n    while True:\n        if u'order[%d][column]' % i not in request.form:\n            break\n        sort_by_num = int(request.form[u'order[%d][column]' % i])\n        sort_order = (\n            u'desc' if request.form[u'order[%d][dir]' %\n                                    i] == u'desc' else u'asc'\n        )\n        sort_list.append(cols[sort_by_num] + u' ' + sort_order)\n        i += 1\n\n    response = datastore_search(\n        None, {\n            u\"q\": search_text,\n            u\"resource_id\": resource_view[u'resource_id'],\n            u\"offset\": offset,\n            u\"limit\": limit,\n            u\"sort\": u', '.join(sort_list),\n            u\"filters\": filters,\n        }\n    )\n\n    return json.dumps({\n        u'draw': draw,\n        u'iTotalRecords': unfiltered_response.get(u'total', 0),\n        u'iTotalDisplayRecords': response.get(u'total', 0),\n        u'aaData': [[text_type(row.get(colname, u''))\n                     for colname in cols]\n                    for row in response[u'records']],\n    })\n\n\ndef filtered_download(resource_view_id):\n    params = json.loads(request.params[u'params'])\n    resource_view = get_action(u'resource_view_show'\n                               )(None, {\n                                   u'id': resource_view_id\n                               })\n\n    search_text = text_type(params[u'search'][u'value'])\n    view_filters = resource_view.get(u'filters', {})\n    user_filters = text_type(params[u'filters'])\n    filters = merge_filters(view_filters, user_filters)\n\n    datastore_search = get_action(u'datastore_search')\n    unfiltered_response = datastore_search(\n        None, {\n            u\"resource_id\": resource_view[u'resource_id'],\n            u\"limit\": 0,\n            u\"filters\": view_filters,\n        }\n    )\n\n    cols = [f[u'id'] for f in unfiltered_response[u'fields']]\n    if u'show_fields' in resource_view:\n        cols = [c for c in cols if c in resource_view[u'show_fields']]\n\n    sort_list = []\n    for order in params[u'order']:\n        sort_by_num = int(order[u'column'])\n        sort_order = (u'desc' if order[u'dir'] == u'desc' else u'asc')\n        sort_list.append(cols[sort_by_num] + u' ' + sort_order)\n\n    cols = [c for (c, v) in zip(cols, params[u'visible']) if v]\n\n    h.redirect_to(\n        h.\n        url_for(u'datastore.dump', resource_id=resource_view[u'resource_id']) +\n        u'?' + urlencode({\n            u'q': search_text,\n            u'sort': u','.join(sort_list),\n            u'filters': json.dumps(filters),\n            u'format': request.params[u'format'],\n            u'fields': u','.join(cols),\n        })\n    )\n\n\ndatatablesview.add_url_rule(\n    u'/datatables/ajax/<resource_view_id>', view_func=ajax, methods=[u'POST']\n)\n\ndatatablesview.add_url_rule(\n    u'/datatables/filtered-download/<resource_view_id>',\n    view_func=filtered_download\n)\n", "path": "ckanext/datatablesview/blueprint.py"}], "after_files": [{"content": "# encoding: utf-8\n\nfrom six.moves.urllib.parse import urlencode\n\nfrom flask import Blueprint\nfrom six import text_type\n\nfrom ckan.common import json\nfrom ckan.plugins.toolkit import get_action, request, h\n\ndatatablesview = Blueprint(u'datatablesview', __name__)\n\n\ndef merge_filters(view_filters, user_filters_str):\n    u'''\n    view filters are built as part of the view, user filters\n    are selected by the user interacting with the view. Any filters\n    selected by user may only tighten filters set in the view,\n    others are ignored.\n\n    >>> merge_filters({\n    ...    u'Department': [u'BTDT'], u'OnTime_Status': [u'ONTIME']},\n    ...    u'CASE_STATUS:Open|CASE_STATUS:Closed|Department:INFO')\n    {u'Department': [u'BTDT'],\n     u'OnTime_Status': [u'ONTIME'],\n     u'CASE_STATUS': [u'Open', u'Closed']}\n    '''\n    filters = dict(view_filters)\n    if not user_filters_str:\n        return filters\n    user_filters = {}\n    for k_v in user_filters_str.split(u'|'):\n        k, sep, v = k_v.partition(u':')\n        if k not in view_filters or v in view_filters[k]:\n            user_filters.setdefault(k, []).append(v)\n    for k in user_filters:\n        filters[k] = user_filters[k]\n    return filters\n\n\ndef ajax(resource_view_id):\n    resource_view = get_action(u'resource_view_show'\n                               )(None, {\n                                   u'id': resource_view_id\n                               })\n\n    draw = int(request.form[u'draw'])\n    search_text = text_type(request.form[u'search[value]'])\n    offset = int(request.form[u'start'])\n    limit = int(request.form[u'length'])\n    view_filters = resource_view.get(u'filters', {})\n    user_filters = text_type(request.form[u'filters'])\n    filters = merge_filters(view_filters, user_filters)\n\n    datastore_search = get_action(u'datastore_search')\n    unfiltered_response = datastore_search(\n        None, {\n            u\"resource_id\": resource_view[u'resource_id'],\n            u\"limit\": 0,\n            u\"filters\": view_filters,\n        }\n    )\n\n    cols = [f[u'id'] for f in unfiltered_response[u'fields']]\n    if u'show_fields' in resource_view:\n        cols = [c for c in cols if c in resource_view[u'show_fields']]\n\n    sort_list = []\n    i = 0\n    while True:\n        if u'order[%d][column]' % i not in request.form:\n            break\n        sort_by_num = int(request.form[u'order[%d][column]' % i])\n        sort_order = (\n            u'desc' if request.form[u'order[%d][dir]' %\n                                    i] == u'desc' else u'asc'\n        )\n        sort_list.append(cols[sort_by_num] + u' ' + sort_order)\n        i += 1\n\n    response = datastore_search(\n        None, {\n            u\"q\": search_text,\n            u\"resource_id\": resource_view[u'resource_id'],\n            u\"offset\": offset,\n            u\"limit\": limit,\n            u\"sort\": u', '.join(sort_list),\n            u\"filters\": filters,\n        }\n    )\n\n    return json.dumps({\n        u'draw': draw,\n        u'iTotalRecords': unfiltered_response.get(u'total', 0),\n        u'iTotalDisplayRecords': response.get(u'total', 0),\n        u'aaData': [[text_type(row.get(colname, u''))\n                     for colname in cols]\n                    for row in response[u'records']],\n    })\n\n\ndef filtered_download(resource_view_id):\n    params = json.loads(request.form[u'params'])\n    resource_view = get_action(u'resource_view_show'\n                               )(None, {\n                                   u'id': resource_view_id\n                               })\n\n    search_text = text_type(params[u'search'][u'value'])\n    view_filters = resource_view.get(u'filters', {})\n    user_filters = text_type(params[u'filters'])\n    filters = merge_filters(view_filters, user_filters)\n\n    datastore_search = get_action(u'datastore_search')\n    unfiltered_response = datastore_search(\n        None, {\n            u\"resource_id\": resource_view[u'resource_id'],\n            u\"limit\": 0,\n            u\"filters\": view_filters,\n        }\n    )\n\n    cols = [f[u'id'] for f in unfiltered_response[u'fields']]\n    if u'show_fields' in resource_view:\n        cols = [c for c in cols if c in resource_view[u'show_fields']]\n\n    sort_list = []\n    for order in params[u'order']:\n        sort_by_num = int(order[u'column'])\n        sort_order = (u'desc' if order[u'dir'] == u'desc' else u'asc')\n        sort_list.append(cols[sort_by_num] + u' ' + sort_order)\n\n    cols = [c for (c, v) in zip(cols, params[u'visible']) if v]\n\n    return h.redirect_to(\n        h.\n        url_for(u'datastore.dump', resource_id=resource_view[u'resource_id']) +\n        u'?' + urlencode({\n            u'q': search_text,\n            u'sort': u','.join(sort_list),\n            u'filters': json.dumps(filters),\n            u'format': request.form[u'format'],\n            u'fields': u','.join(cols),\n        })\n    )\n\n\ndatatablesview.add_url_rule(\n    u'/datatables/ajax/<resource_view_id>', view_func=ajax, methods=[u'POST']\n)\n\ndatatablesview.add_url_rule(\n    u'/datatables/filtered-download/<resource_view_id>',\n    view_func=filtered_download, methods=[u'POST']\n)\n", "path": "ckanext/datatablesview/blueprint.py"}]}