prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Robots.txt can no longer be easily customised
  **CKAN version**
  
  2.9
  
  **Describe the bug**
  
  `robots.txt` was moved back to the `public` directory as part of #4801. However, this reverts the implementation of https://github.com/ckan/ideas-and-roadmap/issues/178 and makes it harder to customise the file (it can still be overridden with a different version, but not using Jinja syntax).
  
  
  </issue>
  <code>
  [start of ckan/views/home.py]
  1 # encoding: utf-8
  2 
  3 from __future__ import annotations
  4 
  5 from urllib.parse import urlencode
  6 from typing import Any, Optional, cast, List, Tuple
  7 
  8 from flask import Blueprint, abort, redirect, request
  9 
  10 import ckan.model as model
  11 import ckan.logic as logic
  12 import ckan.lib.base as base
  13 import ckan.lib.search as search
  14 import ckan.lib.helpers as h
  15 
  16 from ckan.common import g, config, current_user, _
  17 from ckan.types import Context
  18 
  19 
  20 CACHE_PARAMETERS = [u'__cache', u'__no_cache__']
  21 
  22 
  23 home = Blueprint(u'home', __name__)
  24 
  25 
  26 @home.before_request
  27 def before_request() -> None:
  28     u'''set context and check authorization'''
  29     try:
  30         context = cast(Context, {
  31             u'model': model,
  32             u'user': current_user.name,
  33             u'auth_user_obj': current_user})
  34         logic.check_access(u'site_read', context)
  35     except logic.NotAuthorized:
  36         abort(403)
  37 
  38 
  39 def index() -> str:
  40     u'''display home page'''
  41     try:
  42         context = cast(Context, {
  43             u'model': model,
  44             u'session': model.Session,
  45             u'user': current_user.name,
  46             u'auth_user_obj': current_user
  47             }
  48         )
  49 
  50         data_dict: dict[str, Any] = {
  51             u'q': u'*:*',
  52             u'facet.field': h.facets(),
  53             u'rows': 4,
  54             u'start': 0,
  55             u'sort': u'view_recent desc',
  56             u'fq': u'capacity:"public"'}
  57         query = logic.get_action(u'package_search')(context, data_dict)
  58         g.package_count = query['count']
  59         g.datasets = query['results']
  60 
  61         org_label = h.humanize_entity_type(
  62             u'organization',
  63             h.default_group_type(u'organization'),
  64             u'facet label') or _(u'Organizations')
  65 
  66         group_label = h.humanize_entity_type(
  67             u'group',
  68             h.default_group_type(u'group'),
  69             u'facet label') or _(u'Groups')
  70 
  71         g.facet_titles = {
  72             u'organization': org_label,
  73             u'groups': group_label,
  74             u'tags': _(u'Tags'),
  75             u'res_format': _(u'Formats'),
  76             u'license': _(u'Licenses'),
  77         }
  78 
  79     except search.SearchError:
  80         g.package_count = 0
  81 
  82     if current_user.is_authenticated and not current_user.email:
  83         url = h.url_for('user.edit')
  84         msg = _(u'Please <a href="%s">update your profile</a>'
  85                 u' and add your email address. ') % url + \
  86             _(u'%s uses your email address'
  87                 u' if you need to reset your password.') \
  88             % config.get_value(u'ckan.site_title')
  89         h.flash_notice(msg, allow_html=True)
  90     return base.render(u'home/index.html', extra_vars={})
  91 
  92 
  93 def about() -> str:
  94     u''' display about page'''
  95     return base.render(u'home/about.html', extra_vars={})
  96 
  97 
  98 def redirect_locale(target_locale: str, path: Optional[str] = None) -> Any:
  99 
  100     target = f'/{target_locale}/{path}' if path else f'/{target_locale}'
  101 
  102     if request.args:
  103         target += f'?{urlencode(request.args)}'
  104 
  105     return redirect(target, code=308)
  106 
  107 
  108 util_rules: List[Tuple[str, Any]] = [
  109     (u'/', index),
  110     (u'/about', about)
  111 ]
  112 for rule, view_func in util_rules:
  113     home.add_url_rule(rule, view_func=view_func)
  114 
  115 locales_mapping: List[Tuple[str, str]] = [
  116     ('zh_TW', 'zh_Hant_TW'),
  117     ('zh_CN', 'zh_Hans_CN'),
  118     ('no', 'nb_NO'),
  119 ]
  120 
  121 for locale in locales_mapping:
  122 
  123     legacy_locale = locale[0]
  124     new_locale = locale[1]
  125 
  126     home.add_url_rule(
  127         f'/{legacy_locale}/',
  128         view_func=redirect_locale,
  129         defaults={'target_locale': new_locale}
  130     )
  131 
  132     home.add_url_rule(
  133         f'/{legacy_locale}/<path:path>',
  134         view_func=redirect_locale,
  135         defaults={'target_locale': new_locale}
  136     )
  137
  [end of ckan/views/home.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/ckan/views/home.py b/ckan/views/home.py
  --- a/ckan/views/home.py
  +++ b/ckan/views/home.py
  @@ -5,7 +5,7 @@
   from urllib.parse import urlencode
   from typing import Any, Optional, cast, List, Tuple
   
  -from flask import Blueprint, abort, redirect, request
  +from flask import Blueprint, make_response, abort, redirect, request
   
   import ckan.model as model
   import ckan.logic as logic
  @@ -14,7 +14,7 @@
   import ckan.lib.helpers as h
   
   from ckan.common import g, config, current_user, _
  -from ckan.types import Context
  +from ckan.types import Context, Response
   
   
   CACHE_PARAMETERS = [u'__cache', u'__no_cache__']
  @@ -95,6 +95,13 @@
       return base.render(u'home/about.html', extra_vars={})
   
   
  +def robots_txt() -> Response:
  +    '''display robots.txt'''
  +    resp = make_response(base.render('home/robots.txt'))
  +    resp.headers['Content-Type'] = "text/plain; charset=utf-8"
  +    return resp
  +
  +
   def redirect_locale(target_locale: str, path: Optional[str] = None) -> Any:
   
       target = f'/{target_locale}/{path}' if path else f'/{target_locale}'
  @@ -107,7 +114,8 @@
   
   util_rules: List[Tuple[str, Any]] = [
       (u'/', index),
  -    (u'/about', about)
  +    (u'/about', about),
  +    (u'/robots.txt', robots_txt)
   ]
   for rule, view_func in util_rules:
       home.add_url_rule(rule, view_func=view_func)
  
verification_info: {"golden_diff": "diff --git a/ckan/views/home.py b/ckan/views/home.py\n--- a/ckan/views/home.py\n+++ b/ckan/views/home.py\n@@ -5,7 +5,7 @@\n from urllib.parse import urlencode\n from typing import Any, Optional, cast, List, Tuple\n \n-from flask import Blueprint, abort, redirect, request\n+from flask import Blueprint, make_response, abort, redirect, request\n \n import ckan.model as model\n import ckan.logic as logic\n@@ -14,7 +14,7 @@\n import ckan.lib.helpers as h\n \n from ckan.common import g, config, current_user, _\n-from ckan.types import Context\n+from ckan.types import Context, Response\n \n \n CACHE_PARAMETERS = [u'__cache', u'__no_cache__']\n@@ -95,6 +95,13 @@\n     return base.render(u'home/about.html', extra_vars={})\n \n \n+def robots_txt() -> Response:\n+    '''display robots.txt'''\n+    resp = make_response(base.render('home/robots.txt'))\n+    resp.headers['Content-Type'] = \"text/plain; charset=utf-8\"\n+    return resp\n+\n+\n def redirect_locale(target_locale: str, path: Optional[str] = None) -> Any:\n \n     target = f'/{target_locale}/{path}' if path else f'/{target_locale}'\n@@ -107,7 +114,8 @@\n \n util_rules: List[Tuple[str, Any]] = [\n     (u'/', index),\n-    (u'/about', about)\n+    (u'/about', about),\n+    (u'/robots.txt', robots_txt)\n ]\n for rule, view_func in util_rules:\n     home.add_url_rule(rule, view_func=view_func)\n", "issue": "Robots.txt can no longer be easily customised\n**CKAN version**\r\n\r\n2.9\r\n\r\n**Describe the bug**\r\n\r\n`robots.txt` was moved back to the `public` directory as part of #4801. However, this reverts the implementation of https://github.com/ckan/ideas-and-roadmap/issues/178 and makes it harder to customise the file (it can still be overridden with a different version, but not using Jinja syntax).\r\n\n", "before_files": [{"content": "# encoding: utf-8\n\nfrom __future__ import annotations\n\nfrom urllib.parse import urlencode\nfrom typing import Any, Optional, cast, List, Tuple\n\nfrom flask import Blueprint, abort, redirect, request\n\nimport ckan.model as model\nimport ckan.logic as logic\nimport ckan.lib.base as base\nimport ckan.lib.search as search\nimport ckan.lib.helpers as h\n\nfrom ckan.common import g, config, current_user, _\nfrom ckan.types import Context\n\n\nCACHE_PARAMETERS = [u'__cache', u'__no_cache__']\n\n\nhome = Blueprint(u'home', __name__)\n\n\n@home.before_request\ndef before_request() -> None:\n    u'''set context and check authorization'''\n    try:\n        context = cast(Context, {\n            u'model': model,\n            u'user': current_user.name,\n            u'auth_user_obj': current_user})\n        logic.check_access(u'site_read', context)\n    except logic.NotAuthorized:\n        abort(403)\n\n\ndef index() -> str:\n    u'''display home page'''\n    try:\n        context = cast(Context, {\n            u'model': model,\n            u'session': model.Session,\n            u'user': current_user.name,\n            u'auth_user_obj': current_user\n            }\n        )\n\n        data_dict: dict[str, Any] = {\n            u'q': u'*:*',\n            u'facet.field': h.facets(),\n            u'rows': 4,\n            u'start': 0,\n            u'sort': u'view_recent desc',\n            u'fq': u'capacity:\"public\"'}\n        query = logic.get_action(u'package_search')(context, data_dict)\n        g.package_count = query['count']\n        g.datasets = query['results']\n\n        org_label = h.humanize_entity_type(\n            u'organization',\n            h.default_group_type(u'organization'),\n            u'facet label') or _(u'Organizations')\n\n        group_label = h.humanize_entity_type(\n            u'group',\n            h.default_group_type(u'group'),\n            u'facet label') or _(u'Groups')\n\n        g.facet_titles = {\n            u'organization': org_label,\n            u'groups': group_label,\n            u'tags': _(u'Tags'),\n            u'res_format': _(u'Formats'),\n            u'license': _(u'Licenses'),\n        }\n\n    except search.SearchError:\n        g.package_count = 0\n\n    if current_user.is_authenticated and not current_user.email:\n        url = h.url_for('user.edit')\n        msg = _(u'Please <a href=\"%s\">update your profile</a>'\n                u' and add your email address. ') % url + \\\n            _(u'%s uses your email address'\n                u' if you need to reset your password.') \\\n            % config.get_value(u'ckan.site_title')\n        h.flash_notice(msg, allow_html=True)\n    return base.render(u'home/index.html', extra_vars={})\n\n\ndef about() -> str:\n    u''' display about page'''\n    return base.render(u'home/about.html', extra_vars={})\n\n\ndef redirect_locale(target_locale: str, path: Optional[str] = None) -> Any:\n\n    target = f'/{target_locale}/{path}' if path else f'/{target_locale}'\n\n    if request.args:\n        target += f'?{urlencode(request.args)}'\n\n    return redirect(target, code=308)\n\n\nutil_rules: List[Tuple[str, Any]] = [\n    (u'/', index),\n    (u'/about', about)\n]\nfor rule, view_func in util_rules:\n    home.add_url_rule(rule, view_func=view_func)\n\nlocales_mapping: List[Tuple[str, str]] = [\n    ('zh_TW', 'zh_Hant_TW'),\n    ('zh_CN', 'zh_Hans_CN'),\n    ('no', 'nb_NO'),\n]\n\nfor locale in locales_mapping:\n\n    legacy_locale = locale[0]\n    new_locale = locale[1]\n\n    home.add_url_rule(\n        f'/{legacy_locale}/',\n        view_func=redirect_locale,\n        defaults={'target_locale': new_locale}\n    )\n\n    home.add_url_rule(\n        f'/{legacy_locale}/<path:path>',\n        view_func=redirect_locale,\n        defaults={'target_locale': new_locale}\n    )\n", "path": "ckan/views/home.py"}]}