We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Portal Sidebar not displaying in v14-beta.3
The portal sidebar does not display consistently on different kinds of routes. On some pages, the sidebar is not rendered, while on others it is. This has been tested on both existing sites and fresh installs.

/me (no sidebar)
![Screenshot 2022-05-02 211924](https://user-images.githubusercontent.com/9303694/166262480-9ba381fa-2a63-4596-b1a5-f72e1650fb0a.png)


/orders (single link on sidebar)
![Screenshot 2022-05-02 212052](https://user-images.githubusercontent.com/9303694/166262875-bbb2e7d3-95ae-4122-8ab9-bfa246384317.png)


/orders/SPECIFIC-DOC (full sidebar)
![Screenshot 2022-05-02 212148](https://user-images.githubusercontent.com/9303694/166262823-cb14af17-516b-4ddf-a54a-1c1b623aae54.png)



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `frappe/website/page_renderers/template_page.py`
Content:
```
1 import os
2 from importlib.machinery import all_suffixes
3 
4 import click
5 
6 import frappe
7 from frappe.website.page_renderers.base_template_page import BaseTemplatePage
8 from frappe.website.router import get_base_template, get_page_info
9 from frappe.website.utils import (
10 	cache_html,
11 	extract_comment_tag,
12 	extract_title,
13 	get_frontmatter,
14 	get_next_link,
15 	get_sidebar_items,
16 	get_toc,
17 	is_binary_file,
18 )
19 
20 PY_LOADER_SUFFIXES = tuple(all_suffixes())
21 
22 WEBPAGE_PY_MODULE_PROPERTIES = (
23 	"base_template_path",
24 	"template",
25 	"no_cache",
26 	"sitemap",
27 	"condition_field",
28 )
29 
30 COMMENT_PROPERTY_KEY_VALUE_MAP = {
31 	"no-breadcrumbs": ("no_breadcrumbs", 1),
32 	"show-sidebar": ("show_sidebar", 1),
33 	"add-breadcrumbs": ("add_breadcrumbs", 1),
34 	"no-header": ("no_header", 1),
35 	"add-next-prev-links": ("add_next_prev_links", 1),
36 	"no-cache": ("no_cache", 1),
37 	"no-sitemap": ("sitemap", 0),
38 	"sitemap": ("sitemap", 1),
39 }
40 
41 
42 class TemplatePage(BaseTemplatePage):
43 	def __init__(self, path, http_status_code=None):
44 		super().__init__(path=path, http_status_code=http_status_code)
45 		self.set_template_path()
46 
47 	def set_template_path(self):
48 		"""
49 		Searches for file matching the path in the /www
50 		and /templates/pages folders and sets path if match is found
51 		"""
52 		folders = get_start_folders()
53 		for app in reversed(frappe.get_installed_apps()):
54 			app_path = frappe.get_app_path(app)
55 
56 			for dirname in folders:
57 				search_path = os.path.join(app_path, dirname, self.path)
58 				for file_path in self.get_index_path_options(search_path):
59 					if os.path.isfile(file_path) and not is_binary_file(file_path):
60 						self.app = app
61 						self.app_path = app_path
62 						self.file_dir = dirname
63 						self.basename = os.path.splitext(file_path)[0]
64 						self.template_path = os.path.relpath(file_path, self.app_path)
65 						self.basepath = os.path.dirname(file_path)
66 						self.filename = os.path.basename(file_path)
67 						self.name = os.path.splitext(self.filename)[0]
68 						return
69 
70 	def can_render(self):
71 		return (
72 			hasattr(self, "template_path")
73 			and self.template_path
74 			and not self.template_path.endswith(PY_LOADER_SUFFIXES)
75 		)
76 
77 	@staticmethod
78 	def get_index_path_options(search_path):
79 		return (
80 			frappe.as_unicode(f"{search_path}{d}") for d in ("", ".html", ".md", "/index.html", "/index.md")
81 		)
82 
83 	def render(self):
84 		html = self.get_html()
85 		html = self.add_csrf_token(html)
86 		return self.build_response(html)
87 
88 	@cache_html
89 	def get_html(self):
90 		# context object should be separate from self for security
91 		# because it will be accessed via the user defined template
92 		self.init_context()
93 
94 		self.set_pymodule()
95 		self.update_context()
96 		self.setup_template_source()
97 		self.load_colocated_files()
98 		self.set_properties_from_source()
99 		self.post_process_context()
100 
101 		html = self.render_template()
102 		html = self.update_toc(html)
103 
104 		return html
105 
106 	def post_process_context(self):
107 		self.set_user_info()
108 		self.add_sidebar_and_breadcrumbs()
109 		super().post_process_context()
110 
111 	def add_sidebar_and_breadcrumbs(self):
112 		if self.basepath:
113 			self.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath)
114 
115 		if self.context.add_breadcrumbs and not self.context.parents:
116 			parent_path = os.path.dirname(self.path)
117 			if self.path.endswith("index"):
118 				# in case of index page move one directory up for parent path
119 				parent_path = os.path.dirname(parent_path)
120 
121 			for parent_file_path in self.get_index_path_options(parent_path):
122 				parent_file_path = os.path.join(self.app_path, self.file_dir, parent_file_path)
123 				if os.path.isfile(parent_file_path):
124 					parent_page_context = get_page_info(parent_file_path, self.app, self.file_dir)
125 					if parent_page_context:
126 						self.context.parents = [
127 							dict(route=os.path.dirname(self.path), title=parent_page_context.title)
128 						]
129 					break
130 
131 	def set_pymodule(self):
132 		"""
133 		A template may have a python module with a `get_context` method along with it in the
134 		same folder. Also the hyphens will be coverted to underscore for python module names.
135 		This method sets the pymodule_name if it exists.
136 		"""
137 		template_basepath = os.path.splitext(self.template_path)[0]
138 		self.pymodule_name = None
139 
140 		# replace - with _ in the internal modules names
141 		self.pymodule_path = os.path.join(
142 			os.path.dirname(template_basepath),
143 			os.path.basename(template_basepath.replace("-", "_")) + ".py",
144 		)
145 
146 		if os.path.exists(os.path.join(self.app_path, self.pymodule_path)):
147 			self.pymodule_name = self.app + "." + self.pymodule_path.replace(os.path.sep, ".")[:-3]
148 
149 	def setup_template_source(self):
150 		"""Setup template source, frontmatter and markdown conversion"""
151 		self.source = self.get_raw_template()
152 		self.extract_frontmatter()
153 		self.convert_from_markdown()
154 
155 	def update_context(self):
156 		self.set_page_properties()
157 		self.context.build_version = frappe.utils.get_build_version()
158 
159 		if self.pymodule_name:
160 			self.pymodule = frappe.get_module(self.pymodule_name)
161 			self.set_pymodule_properties()
162 
163 			data = self.run_pymodule_method("get_context")
164 			# some methods may return a "context" object
165 			if data:
166 				self.context.update(data)
167 			# TODO: self.context.children = self.run_pymodule_method('get_children')
168 
169 		self.context.developer_mode = frappe.conf.developer_mode
170 		if self.context.http_status_code:
171 			self.http_status_code = self.context.http_status_code
172 
173 	def set_pymodule_properties(self):
174 		for prop in WEBPAGE_PY_MODULE_PROPERTIES:
175 			if hasattr(self.pymodule, prop):
176 				self.context[prop] = getattr(self.pymodule, prop)
177 
178 	def set_page_properties(self):
179 		self.context.base_template = self.context.base_template or get_base_template(self.path)
180 		self.context.basepath = self.basepath
181 		self.context.basename = self.basename
182 		self.context.name = self.name
183 		self.context.path = self.path
184 		self.context.route = self.path
185 		self.context.template = self.template_path
186 
187 	def set_properties_from_source(self):
188 		if not self.source:
189 			return
190 		context = self.context
191 		if not context.title:
192 			context.title = extract_title(self.source, self.path)
193 
194 		base_template = extract_comment_tag(self.source, "base_template")
195 		if base_template:
196 			context.base_template = base_template
197 
198 		if (
199 			context.base_template
200 			and "{%- extends" not in self.source
201 			and "{% extends" not in self.source
202 			and "</body>" not in self.source
203 		):
204 			self.source = """{{% extends "{0}" %}}
205 				{{% block page_content %}}{1}{{% endblock %}}""".format(
206 				context.base_template, self.source
207 			)
208 
209 		self.set_properties_via_comments()
210 
211 	def set_properties_via_comments(self):
212 		for comment, (context_key, value) in COMMENT_PROPERTY_KEY_VALUE_MAP.items():
213 			comment_tag = f"<!-- {comment} -->"
214 			if comment_tag in self.source:
215 				self.context[context_key] = value
216 				click.echo(f"\n⚠️  DEPRECATION WARNING: {comment_tag} will be deprecated on 2021-12-31.")
217 				click.echo(f"Please remove it from {self.template_path} in {self.app}")
218 
219 	def run_pymodule_method(self, method_name):
220 		if hasattr(self.pymodule, method_name):
221 			import inspect
222 
223 			method = getattr(self.pymodule, method_name)
224 			if inspect.getfullargspec(method).args:
225 				return method(self.context)
226 			else:
227 				return method()
228 
229 	def render_template(self):
230 		if self.template_path.endswith("min.js"):
231 			html = self.source  # static
232 		else:
233 			if self.context.safe_render is not None:
234 				safe_render = self.context.safe_render
235 			else:
236 				safe_render = True
237 
238 			html = frappe.render_template(self.source, self.context, safe_render=safe_render)
239 
240 		return html
241 
242 	def extends_template(self):
243 		return self.template_path.endswith((".html", ".md")) and (
244 			"{%- extends" in self.source or "{% extends" in self.source
245 		)
246 
247 	def get_raw_template(self):
248 		return frappe.get_jloader().get_source(frappe.get_jenv(), self.context.template)[0]
249 
250 	def load_colocated_files(self):
251 		"""load co-located css/js files with the same name"""
252 		js_path = self.basename + ".js"
253 		if os.path.exists(js_path) and "{% block script %}" not in self.source:
254 			self.context.colocated_js = self.get_colocated_file(js_path)
255 
256 		css_path = self.basename + ".css"
257 		if os.path.exists(css_path) and "{% block style %}" not in self.source:
258 			self.context.colocated_css = self.get_colocated_file(css_path)
259 
260 	def get_colocated_file(self, path):
261 		with open(path, encoding="utf-8") as f:
262 			return f.read()
263 
264 	def extract_frontmatter(self):
265 		if not self.template_path.endswith((".md", ".html")):
266 			return
267 
268 		try:
269 			# values will be used to update self
270 			res = get_frontmatter(self.source)
271 			if res["attributes"]:
272 				self.context.update(res["attributes"])
273 				self.source = res["body"]
274 		except Exception:
275 			pass
276 
277 	def convert_from_markdown(self):
278 		if self.template_path.endswith(".md"):
279 			self.source = frappe.utils.md_to_html(self.source)
280 			self.context.page_toc_html = self.source.toc_html
281 
282 			if not self.context.show_sidebar:
283 				self.source = '<div class="from-markdown">' + self.source + "</div>"
284 
285 	def update_toc(self, html):
286 		if "{index}" in html:
287 			html = html.replace("{index}", get_toc(self.path))
288 
289 		if "{next}" in html:
290 			html = html.replace("{next}", get_next_link(self.path))
291 
292 		return html
293 
294 	def set_standard_path(self, path):
295 		self.app = "frappe"
296 		self.app_path = frappe.get_app_path("frappe")
297 		self.path = path
298 		self.template_path = f"www/{path}.html"
299 
300 	def set_missing_values(self):
301 		super().set_missing_values()
302 		# for backward compatibility
303 		self.context.docs_base_url = "/docs"
304 
305 	def set_user_info(self):
306 		from frappe.utils.user import get_fullname_and_avatar
307 
308 		info = get_fullname_and_avatar(frappe.session.user)
309 		self.context["fullname"] = info.fullname
310 		self.context["user_image"] = info.avatar
311 		self.context["user"] = info.name
312 
313 
314 def get_start_folders():
315 	return frappe.local.flags.web_pages_folders or ("www", "templates/pages")
316
```
--- 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/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py
--- a/frappe/website/page_renderers/template_page.py
+++ b/frappe/website/page_renderers/template_page.py
@@ -109,8 +109,7 @@
 		super().post_process_context()
 
 	def add_sidebar_and_breadcrumbs(self):
-		if self.basepath:
-			self.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath)
+		self.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath)
 
 		if self.context.add_breadcrumbs and not self.context.parents:
 			parent_path = os.path.dirname(self.path)

{"golden_diff": "diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py\n--- a/frappe/website/page_renderers/template_page.py\n+++ b/frappe/website/page_renderers/template_page.py\n@@ -109,8 +109,7 @@\n \t\tsuper().post_process_context()\n \n \tdef add_sidebar_and_breadcrumbs(self):\n-\t\tif self.basepath:\n-\t\t\tself.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath)\n+\t\tself.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath)\n \n \t\tif self.context.add_breadcrumbs and not self.context.parents:\n \t\t\tparent_path = os.path.dirname(self.path)\n", "issue": "Portal Sidebar not displaying in v14-beta.3\nThe portal sidebar does not display consistently on different kinds of routes. On some pages, the sidebar is not rendered, while on others it is. This has been tested on both existing sites and fresh installs.\r\n\r\n/me (no sidebar)\r\n![Screenshot 2022-05-02 211924](https://user-images.githubusercontent.com/9303694/166262480-9ba381fa-2a63-4596-b1a5-f72e1650fb0a.png)\r\n\r\n\r\n/orders (single link on sidebar)\r\n![Screenshot 2022-05-02 212052](https://user-images.githubusercontent.com/9303694/166262875-bbb2e7d3-95ae-4122-8ab9-bfa246384317.png)\r\n\r\n\r\n/orders/SPECIFIC-DOC (full sidebar)\r\n![Screenshot 2022-05-02 212148](https://user-images.githubusercontent.com/9303694/166262823-cb14af17-516b-4ddf-a54a-1c1b623aae54.png)\r\n\r\n\n", "before_files": [{"content": "import os\nfrom importlib.machinery import all_suffixes\n\nimport click\n\nimport frappe\nfrom frappe.website.page_renderers.base_template_page import BaseTemplatePage\nfrom frappe.website.router import get_base_template, get_page_info\nfrom frappe.website.utils import (\n\tcache_html,\n\textract_comment_tag,\n\textract_title,\n\tget_frontmatter,\n\tget_next_link,\n\tget_sidebar_items,\n\tget_toc,\n\tis_binary_file,\n)\n\nPY_LOADER_SUFFIXES = tuple(all_suffixes())\n\nWEBPAGE_PY_MODULE_PROPERTIES = (\n\t\"base_template_path\",\n\t\"template\",\n\t\"no_cache\",\n\t\"sitemap\",\n\t\"condition_field\",\n)\n\nCOMMENT_PROPERTY_KEY_VALUE_MAP = {\n\t\"no-breadcrumbs\": (\"no_breadcrumbs\", 1),\n\t\"show-sidebar\": (\"show_sidebar\", 1),\n\t\"add-breadcrumbs\": (\"add_breadcrumbs\", 1),\n\t\"no-header\": (\"no_header\", 1),\n\t\"add-next-prev-links\": (\"add_next_prev_links\", 1),\n\t\"no-cache\": (\"no_cache\", 1),\n\t\"no-sitemap\": (\"sitemap\", 0),\n\t\"sitemap\": (\"sitemap\", 1),\n}\n\n\nclass TemplatePage(BaseTemplatePage):\n\tdef __init__(self, path, http_status_code=None):\n\t\tsuper().__init__(path=path, http_status_code=http_status_code)\n\t\tself.set_template_path()\n\n\tdef set_template_path(self):\n\t\t\"\"\"\n\t\tSearches for file matching the path in the /www\n\t\tand /templates/pages folders and sets path if match is found\n\t\t\"\"\"\n\t\tfolders = get_start_folders()\n\t\tfor app in reversed(frappe.get_installed_apps()):\n\t\t\tapp_path = frappe.get_app_path(app)\n\n\t\t\tfor dirname in folders:\n\t\t\t\tsearch_path = os.path.join(app_path, dirname, self.path)\n\t\t\t\tfor file_path in self.get_index_path_options(search_path):\n\t\t\t\t\tif os.path.isfile(file_path) and not is_binary_file(file_path):\n\t\t\t\t\t\tself.app = app\n\t\t\t\t\t\tself.app_path = app_path\n\t\t\t\t\t\tself.file_dir = dirname\n\t\t\t\t\t\tself.basename = os.path.splitext(file_path)[0]\n\t\t\t\t\t\tself.template_path = os.path.relpath(file_path, self.app_path)\n\t\t\t\t\t\tself.basepath = os.path.dirname(file_path)\n\t\t\t\t\t\tself.filename = os.path.basename(file_path)\n\t\t\t\t\t\tself.name = os.path.splitext(self.filename)[0]\n\t\t\t\t\t\treturn\n\n\tdef can_render(self):\n\t\treturn (\n\t\t\thasattr(self, \"template_path\")\n\t\t\tand self.template_path\n\t\t\tand not self.template_path.endswith(PY_LOADER_SUFFIXES)\n\t\t)\n\n\t@staticmethod\n\tdef get_index_path_options(search_path):\n\t\treturn (\n\t\t\tfrappe.as_unicode(f\"{search_path}{d}\") for d in (\"\", \".html\", \".md\", \"/index.html\", \"/index.md\")\n\t\t)\n\n\tdef render(self):\n\t\thtml = self.get_html()\n\t\thtml = self.add_csrf_token(html)\n\t\treturn self.build_response(html)\n\n\t@cache_html\n\tdef get_html(self):\n\t\t# context object should be separate from self for security\n\t\t# because it will be accessed via the user defined template\n\t\tself.init_context()\n\n\t\tself.set_pymodule()\n\t\tself.update_context()\n\t\tself.setup_template_source()\n\t\tself.load_colocated_files()\n\t\tself.set_properties_from_source()\n\t\tself.post_process_context()\n\n\t\thtml = self.render_template()\n\t\thtml = self.update_toc(html)\n\n\t\treturn html\n\n\tdef post_process_context(self):\n\t\tself.set_user_info()\n\t\tself.add_sidebar_and_breadcrumbs()\n\t\tsuper().post_process_context()\n\n\tdef add_sidebar_and_breadcrumbs(self):\n\t\tif self.basepath:\n\t\t\tself.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath)\n\n\t\tif self.context.add_breadcrumbs and not self.context.parents:\n\t\t\tparent_path = os.path.dirname(self.path)\n\t\t\tif self.path.endswith(\"index\"):\n\t\t\t\t# in case of index page move one directory up for parent path\n\t\t\t\tparent_path = os.path.dirname(parent_path)\n\n\t\t\tfor parent_file_path in self.get_index_path_options(parent_path):\n\t\t\t\tparent_file_path = os.path.join(self.app_path, self.file_dir, parent_file_path)\n\t\t\t\tif os.path.isfile(parent_file_path):\n\t\t\t\t\tparent_page_context = get_page_info(parent_file_path, self.app, self.file_dir)\n\t\t\t\t\tif parent_page_context:\n\t\t\t\t\t\tself.context.parents = [\n\t\t\t\t\t\t\tdict(route=os.path.dirname(self.path), title=parent_page_context.title)\n\t\t\t\t\t\t]\n\t\t\t\t\tbreak\n\n\tdef set_pymodule(self):\n\t\t\"\"\"\n\t\tA template may have a python module with a `get_context` method along with it in the\n\t\tsame folder. Also the hyphens will be coverted to underscore for python module names.\n\t\tThis method sets the pymodule_name if it exists.\n\t\t\"\"\"\n\t\ttemplate_basepath = os.path.splitext(self.template_path)[0]\n\t\tself.pymodule_name = None\n\n\t\t# replace - with _ in the internal modules names\n\t\tself.pymodule_path = os.path.join(\n\t\t\tos.path.dirname(template_basepath),\n\t\t\tos.path.basename(template_basepath.replace(\"-\", \"_\")) + \".py\",\n\t\t)\n\n\t\tif os.path.exists(os.path.join(self.app_path, self.pymodule_path)):\n\t\t\tself.pymodule_name = self.app + \".\" + self.pymodule_path.replace(os.path.sep, \".\")[:-3]\n\n\tdef setup_template_source(self):\n\t\t\"\"\"Setup template source, frontmatter and markdown conversion\"\"\"\n\t\tself.source = self.get_raw_template()\n\t\tself.extract_frontmatter()\n\t\tself.convert_from_markdown()\n\n\tdef update_context(self):\n\t\tself.set_page_properties()\n\t\tself.context.build_version = frappe.utils.get_build_version()\n\n\t\tif self.pymodule_name:\n\t\t\tself.pymodule = frappe.get_module(self.pymodule_name)\n\t\t\tself.set_pymodule_properties()\n\n\t\t\tdata = self.run_pymodule_method(\"get_context\")\n\t\t\t# some methods may return a \"context\" object\n\t\t\tif data:\n\t\t\t\tself.context.update(data)\n\t\t\t# TODO: self.context.children = self.run_pymodule_method('get_children')\n\n\t\tself.context.developer_mode = frappe.conf.developer_mode\n\t\tif self.context.http_status_code:\n\t\t\tself.http_status_code = self.context.http_status_code\n\n\tdef set_pymodule_properties(self):\n\t\tfor prop in WEBPAGE_PY_MODULE_PROPERTIES:\n\t\t\tif hasattr(self.pymodule, prop):\n\t\t\t\tself.context[prop] = getattr(self.pymodule, prop)\n\n\tdef set_page_properties(self):\n\t\tself.context.base_template = self.context.base_template or get_base_template(self.path)\n\t\tself.context.basepath = self.basepath\n\t\tself.context.basename = self.basename\n\t\tself.context.name = self.name\n\t\tself.context.path = self.path\n\t\tself.context.route = self.path\n\t\tself.context.template = self.template_path\n\n\tdef set_properties_from_source(self):\n\t\tif not self.source:\n\t\t\treturn\n\t\tcontext = self.context\n\t\tif not context.title:\n\t\t\tcontext.title = extract_title(self.source, self.path)\n\n\t\tbase_template = extract_comment_tag(self.source, \"base_template\")\n\t\tif base_template:\n\t\t\tcontext.base_template = base_template\n\n\t\tif (\n\t\t\tcontext.base_template\n\t\t\tand \"{%- extends\" not in self.source\n\t\t\tand \"{% extends\" not in self.source\n\t\t\tand \"</body>\" not in self.source\n\t\t):\n\t\t\tself.source = \"\"\"{{% extends \"{0}\" %}}\n\t\t\t\t{{% block page_content %}}{1}{{% endblock %}}\"\"\".format(\n\t\t\t\tcontext.base_template, self.source\n\t\t\t)\n\n\t\tself.set_properties_via_comments()\n\n\tdef set_properties_via_comments(self):\n\t\tfor comment, (context_key, value) in COMMENT_PROPERTY_KEY_VALUE_MAP.items():\n\t\t\tcomment_tag = f\"<!-- {comment} -->\"\n\t\t\tif comment_tag in self.source:\n\t\t\t\tself.context[context_key] = value\n\t\t\t\tclick.echo(f\"\\n\u26a0\ufe0f  DEPRECATION WARNING: {comment_tag} will be deprecated on 2021-12-31.\")\n\t\t\t\tclick.echo(f\"Please remove it from {self.template_path} in {self.app}\")\n\n\tdef run_pymodule_method(self, method_name):\n\t\tif hasattr(self.pymodule, method_name):\n\t\t\timport inspect\n\n\t\t\tmethod = getattr(self.pymodule, method_name)\n\t\t\tif inspect.getfullargspec(method).args:\n\t\t\t\treturn method(self.context)\n\t\t\telse:\n\t\t\t\treturn method()\n\n\tdef render_template(self):\n\t\tif self.template_path.endswith(\"min.js\"):\n\t\t\thtml = self.source  # static\n\t\telse:\n\t\t\tif self.context.safe_render is not None:\n\t\t\t\tsafe_render = self.context.safe_render\n\t\t\telse:\n\t\t\t\tsafe_render = True\n\n\t\t\thtml = frappe.render_template(self.source, self.context, safe_render=safe_render)\n\n\t\treturn html\n\n\tdef extends_template(self):\n\t\treturn self.template_path.endswith((\".html\", \".md\")) and (\n\t\t\t\"{%- extends\" in self.source or \"{% extends\" in self.source\n\t\t)\n\n\tdef get_raw_template(self):\n\t\treturn frappe.get_jloader().get_source(frappe.get_jenv(), self.context.template)[0]\n\n\tdef load_colocated_files(self):\n\t\t\"\"\"load co-located css/js files with the same name\"\"\"\n\t\tjs_path = self.basename + \".js\"\n\t\tif os.path.exists(js_path) and \"{% block script %}\" not in self.source:\n\t\t\tself.context.colocated_js = self.get_colocated_file(js_path)\n\n\t\tcss_path = self.basename + \".css\"\n\t\tif os.path.exists(css_path) and \"{% block style %}\" not in self.source:\n\t\t\tself.context.colocated_css = self.get_colocated_file(css_path)\n\n\tdef get_colocated_file(self, path):\n\t\twith open(path, encoding=\"utf-8\") as f:\n\t\t\treturn f.read()\n\n\tdef extract_frontmatter(self):\n\t\tif not self.template_path.endswith((\".md\", \".html\")):\n\t\t\treturn\n\n\t\ttry:\n\t\t\t# values will be used to update self\n\t\t\tres = get_frontmatter(self.source)\n\t\t\tif res[\"attributes\"]:\n\t\t\t\tself.context.update(res[\"attributes\"])\n\t\t\t\tself.source = res[\"body\"]\n\t\texcept Exception:\n\t\t\tpass\n\n\tdef convert_from_markdown(self):\n\t\tif self.template_path.endswith(\".md\"):\n\t\t\tself.source = frappe.utils.md_to_html(self.source)\n\t\t\tself.context.page_toc_html = self.source.toc_html\n\n\t\t\tif not self.context.show_sidebar:\n\t\t\t\tself.source = '<div class=\"from-markdown\">' + self.source + \"</div>\"\n\n\tdef update_toc(self, html):\n\t\tif \"{index}\" in html:\n\t\t\thtml = html.replace(\"{index}\", get_toc(self.path))\n\n\t\tif \"{next}\" in html:\n\t\t\thtml = html.replace(\"{next}\", get_next_link(self.path))\n\n\t\treturn html\n\n\tdef set_standard_path(self, path):\n\t\tself.app = \"frappe\"\n\t\tself.app_path = frappe.get_app_path(\"frappe\")\n\t\tself.path = path\n\t\tself.template_path = f\"www/{path}.html\"\n\n\tdef set_missing_values(self):\n\t\tsuper().set_missing_values()\n\t\t# for backward compatibility\n\t\tself.context.docs_base_url = \"/docs\"\n\n\tdef set_user_info(self):\n\t\tfrom frappe.utils.user import get_fullname_and_avatar\n\n\t\tinfo = get_fullname_and_avatar(frappe.session.user)\n\t\tself.context[\"fullname\"] = info.fullname\n\t\tself.context[\"user_image\"] = info.avatar\n\t\tself.context[\"user\"] = info.name\n\n\ndef get_start_folders():\n\treturn frappe.local.flags.web_pages_folders or (\"www\", \"templates/pages\")\n", "path": "frappe/website/page_renderers/template_page.py"}], "after_files": [{"content": "import os\nfrom importlib.machinery import all_suffixes\n\nimport click\n\nimport frappe\nfrom frappe.website.page_renderers.base_template_page import BaseTemplatePage\nfrom frappe.website.router import get_base_template, get_page_info\nfrom frappe.website.utils import (\n\tcache_html,\n\textract_comment_tag,\n\textract_title,\n\tget_frontmatter,\n\tget_next_link,\n\tget_sidebar_items,\n\tget_toc,\n\tis_binary_file,\n)\n\nPY_LOADER_SUFFIXES = tuple(all_suffixes())\n\nWEBPAGE_PY_MODULE_PROPERTIES = (\n\t\"base_template_path\",\n\t\"template\",\n\t\"no_cache\",\n\t\"sitemap\",\n\t\"condition_field\",\n)\n\nCOMMENT_PROPERTY_KEY_VALUE_MAP = {\n\t\"no-breadcrumbs\": (\"no_breadcrumbs\", 1),\n\t\"show-sidebar\": (\"show_sidebar\", 1),\n\t\"add-breadcrumbs\": (\"add_breadcrumbs\", 1),\n\t\"no-header\": (\"no_header\", 1),\n\t\"add-next-prev-links\": (\"add_next_prev_links\", 1),\n\t\"no-cache\": (\"no_cache\", 1),\n\t\"no-sitemap\": (\"sitemap\", 0),\n\t\"sitemap\": (\"sitemap\", 1),\n}\n\n\nclass TemplatePage(BaseTemplatePage):\n\tdef __init__(self, path, http_status_code=None):\n\t\tsuper().__init__(path=path, http_status_code=http_status_code)\n\t\tself.set_template_path()\n\n\tdef set_template_path(self):\n\t\t\"\"\"\n\t\tSearches for file matching the path in the /www\n\t\tand /templates/pages folders and sets path if match is found\n\t\t\"\"\"\n\t\tfolders = get_start_folders()\n\t\tfor app in reversed(frappe.get_installed_apps()):\n\t\t\tapp_path = frappe.get_app_path(app)\n\n\t\t\tfor dirname in folders:\n\t\t\t\tsearch_path = os.path.join(app_path, dirname, self.path)\n\t\t\t\tfor file_path in self.get_index_path_options(search_path):\n\t\t\t\t\tif os.path.isfile(file_path) and not is_binary_file(file_path):\n\t\t\t\t\t\tself.app = app\n\t\t\t\t\t\tself.app_path = app_path\n\t\t\t\t\t\tself.file_dir = dirname\n\t\t\t\t\t\tself.basename = os.path.splitext(file_path)[0]\n\t\t\t\t\t\tself.template_path = os.path.relpath(file_path, self.app_path)\n\t\t\t\t\t\tself.basepath = os.path.dirname(file_path)\n\t\t\t\t\t\tself.filename = os.path.basename(file_path)\n\t\t\t\t\t\tself.name = os.path.splitext(self.filename)[0]\n\t\t\t\t\t\treturn\n\n\tdef can_render(self):\n\t\treturn (\n\t\t\thasattr(self, \"template_path\")\n\t\t\tand self.template_path\n\t\t\tand not self.template_path.endswith(PY_LOADER_SUFFIXES)\n\t\t)\n\n\t@staticmethod\n\tdef get_index_path_options(search_path):\n\t\treturn (\n\t\t\tfrappe.as_unicode(f\"{search_path}{d}\") for d in (\"\", \".html\", \".md\", \"/index.html\", \"/index.md\")\n\t\t)\n\n\tdef render(self):\n\t\thtml = self.get_html()\n\t\thtml = self.add_csrf_token(html)\n\t\treturn self.build_response(html)\n\n\t@cache_html\n\tdef get_html(self):\n\t\t# context object should be separate from self for security\n\t\t# because it will be accessed via the user defined template\n\t\tself.init_context()\n\n\t\tself.set_pymodule()\n\t\tself.update_context()\n\t\tself.setup_template_source()\n\t\tself.load_colocated_files()\n\t\tself.set_properties_from_source()\n\t\tself.post_process_context()\n\n\t\thtml = self.render_template()\n\t\thtml = self.update_toc(html)\n\n\t\treturn html\n\n\tdef post_process_context(self):\n\t\tself.set_user_info()\n\t\tself.add_sidebar_and_breadcrumbs()\n\t\tsuper().post_process_context()\n\n\tdef add_sidebar_and_breadcrumbs(self):\n\t\tself.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath)\n\n\t\tif self.context.add_breadcrumbs and not self.context.parents:\n\t\t\tparent_path = os.path.dirname(self.path)\n\t\t\tif self.path.endswith(\"index\"):\n\t\t\t\t# in case of index page move one directory up for parent path\n\t\t\t\tparent_path = os.path.dirname(parent_path)\n\n\t\t\tfor parent_file_path in self.get_index_path_options(parent_path):\n\t\t\t\tparent_file_path = os.path.join(self.app_path, self.file_dir, parent_file_path)\n\t\t\t\tif os.path.isfile(parent_file_path):\n\t\t\t\t\tparent_page_context = get_page_info(parent_file_path, self.app, self.file_dir)\n\t\t\t\t\tif parent_page_context:\n\t\t\t\t\t\tself.context.parents = [\n\t\t\t\t\t\t\tdict(route=os.path.dirname(self.path), title=parent_page_context.title)\n\t\t\t\t\t\t]\n\t\t\t\t\tbreak\n\n\tdef set_pymodule(self):\n\t\t\"\"\"\n\t\tA template may have a python module with a `get_context` method along with it in the\n\t\tsame folder. Also the hyphens will be coverted to underscore for python module names.\n\t\tThis method sets the pymodule_name if it exists.\n\t\t\"\"\"\n\t\ttemplate_basepath = os.path.splitext(self.template_path)[0]\n\t\tself.pymodule_name = None\n\n\t\t# replace - with _ in the internal modules names\n\t\tself.pymodule_path = os.path.join(\n\t\t\tos.path.dirname(template_basepath),\n\t\t\tos.path.basename(template_basepath.replace(\"-\", \"_\")) + \".py\",\n\t\t)\n\n\t\tif os.path.exists(os.path.join(self.app_path, self.pymodule_path)):\n\t\t\tself.pymodule_name = self.app + \".\" + self.pymodule_path.replace(os.path.sep, \".\")[:-3]\n\n\tdef setup_template_source(self):\n\t\t\"\"\"Setup template source, frontmatter and markdown conversion\"\"\"\n\t\tself.source = self.get_raw_template()\n\t\tself.extract_frontmatter()\n\t\tself.convert_from_markdown()\n\n\tdef update_context(self):\n\t\tself.set_page_properties()\n\t\tself.context.build_version = frappe.utils.get_build_version()\n\n\t\tif self.pymodule_name:\n\t\t\tself.pymodule = frappe.get_module(self.pymodule_name)\n\t\t\tself.set_pymodule_properties()\n\n\t\t\tdata = self.run_pymodule_method(\"get_context\")\n\t\t\t# some methods may return a \"context\" object\n\t\t\tif data:\n\t\t\t\tself.context.update(data)\n\t\t\t# TODO: self.context.children = self.run_pymodule_method('get_children')\n\n\t\tself.context.developer_mode = frappe.conf.developer_mode\n\t\tif self.context.http_status_code:\n\t\t\tself.http_status_code = self.context.http_status_code\n\n\tdef set_pymodule_properties(self):\n\t\tfor prop in WEBPAGE_PY_MODULE_PROPERTIES:\n\t\t\tif hasattr(self.pymodule, prop):\n\t\t\t\tself.context[prop] = getattr(self.pymodule, prop)\n\n\tdef set_page_properties(self):\n\t\tself.context.base_template = self.context.base_template or get_base_template(self.path)\n\t\tself.context.basepath = self.basepath\n\t\tself.context.basename = self.basename\n\t\tself.context.name = self.name\n\t\tself.context.path = self.path\n\t\tself.context.route = self.path\n\t\tself.context.template = self.template_path\n\n\tdef set_properties_from_source(self):\n\t\tif not self.source:\n\t\t\treturn\n\t\tcontext = self.context\n\t\tif not context.title:\n\t\t\tcontext.title = extract_title(self.source, self.path)\n\n\t\tbase_template = extract_comment_tag(self.source, \"base_template\")\n\t\tif base_template:\n\t\t\tcontext.base_template = base_template\n\n\t\tif (\n\t\t\tcontext.base_template\n\t\t\tand \"{%- extends\" not in self.source\n\t\t\tand \"{% extends\" not in self.source\n\t\t\tand \"</body>\" not in self.source\n\t\t):\n\t\t\tself.source = \"\"\"{{% extends \"{0}\" %}}\n\t\t\t\t{{% block page_content %}}{1}{{% endblock %}}\"\"\".format(\n\t\t\t\tcontext.base_template, self.source\n\t\t\t)\n\n\t\tself.set_properties_via_comments()\n\n\tdef set_properties_via_comments(self):\n\t\tfor comment, (context_key, value) in COMMENT_PROPERTY_KEY_VALUE_MAP.items():\n\t\t\tcomment_tag = f\"<!-- {comment} -->\"\n\t\t\tif comment_tag in self.source:\n\t\t\t\tself.context[context_key] = value\n\t\t\t\tclick.echo(f\"\\n\u26a0\ufe0f  DEPRECATION WARNING: {comment_tag} will be deprecated on 2021-12-31.\")\n\t\t\t\tclick.echo(f\"Please remove it from {self.template_path} in {self.app}\")\n\n\tdef run_pymodule_method(self, method_name):\n\t\tif hasattr(self.pymodule, method_name):\n\t\t\timport inspect\n\n\t\t\tmethod = getattr(self.pymodule, method_name)\n\t\t\tif inspect.getfullargspec(method).args:\n\t\t\t\treturn method(self.context)\n\t\t\telse:\n\t\t\t\treturn method()\n\n\tdef render_template(self):\n\t\tif self.template_path.endswith(\"min.js\"):\n\t\t\thtml = self.source  # static\n\t\telse:\n\t\t\tif self.context.safe_render is not None:\n\t\t\t\tsafe_render = self.context.safe_render\n\t\t\telse:\n\t\t\t\tsafe_render = True\n\n\t\t\thtml = frappe.render_template(self.source, self.context, safe_render=safe_render)\n\n\t\treturn html\n\n\tdef extends_template(self):\n\t\treturn self.template_path.endswith((\".html\", \".md\")) and (\n\t\t\t\"{%- extends\" in self.source or \"{% extends\" in self.source\n\t\t)\n\n\tdef get_raw_template(self):\n\t\treturn frappe.get_jloader().get_source(frappe.get_jenv(), self.context.template)[0]\n\n\tdef load_colocated_files(self):\n\t\t\"\"\"load co-located css/js files with the same name\"\"\"\n\t\tjs_path = self.basename + \".js\"\n\t\tif os.path.exists(js_path) and \"{% block script %}\" not in self.source:\n\t\t\tself.context.colocated_js = self.get_colocated_file(js_path)\n\n\t\tcss_path = self.basename + \".css\"\n\t\tif os.path.exists(css_path) and \"{% block style %}\" not in self.source:\n\t\t\tself.context.colocated_css = self.get_colocated_file(css_path)\n\n\tdef get_colocated_file(self, path):\n\t\twith open(path, encoding=\"utf-8\") as f:\n\t\t\treturn f.read()\n\n\tdef extract_frontmatter(self):\n\t\tif not self.template_path.endswith((\".md\", \".html\")):\n\t\t\treturn\n\n\t\ttry:\n\t\t\t# values will be used to update self\n\t\t\tres = get_frontmatter(self.source)\n\t\t\tif res[\"attributes\"]:\n\t\t\t\tself.context.update(res[\"attributes\"])\n\t\t\t\tself.source = res[\"body\"]\n\t\texcept Exception:\n\t\t\tpass\n\n\tdef convert_from_markdown(self):\n\t\tif self.template_path.endswith(\".md\"):\n\t\t\tself.source = frappe.utils.md_to_html(self.source)\n\t\t\tself.context.page_toc_html = self.source.toc_html\n\n\t\t\tif not self.context.show_sidebar:\n\t\t\t\tself.source = '<div class=\"from-markdown\">' + self.source + \"</div>\"\n\n\tdef update_toc(self, html):\n\t\tif \"{index}\" in html:\n\t\t\thtml = html.replace(\"{index}\", get_toc(self.path))\n\n\t\tif \"{next}\" in html:\n\t\t\thtml = html.replace(\"{next}\", get_next_link(self.path))\n\n\t\treturn html\n\n\tdef set_standard_path(self, path):\n\t\tself.app = \"frappe\"\n\t\tself.app_path = frappe.get_app_path(\"frappe\")\n\t\tself.path = path\n\t\tself.template_path = f\"www/{path}.html\"\n\n\tdef set_missing_values(self):\n\t\tsuper().set_missing_values()\n\t\t# for backward compatibility\n\t\tself.context.docs_base_url = \"/docs\"\n\n\tdef set_user_info(self):\n\t\tfrom frappe.utils.user import get_fullname_and_avatar\n\n\t\tinfo = get_fullname_and_avatar(frappe.session.user)\n\t\tself.context[\"fullname\"] = info.fullname\n\t\tself.context[\"user_image\"] = info.avatar\n\t\tself.context[\"user\"] = info.name\n\n\ndef get_start_folders():\n\treturn frappe.local.flags.web_pages_folders or (\"www\", \"templates/pages\")\n", "path": "frappe/website/page_renderers/template_page.py"}]}