Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

317 рядки
8.8 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. import io
  4. import os
  5. import re
  6. from werkzeug.routing import Map, NotFound, Rule
  7. import frappe
  8. from frappe.website.utils import extract_title, get_frontmatter
  9. def get_page_info_from_web_page_with_dynamic_routes(path):
  10. """
  11. Query Web Page with dynamic_route = 1 and evaluate if any of the routes match
  12. """
  13. rules, page_info = [], {}
  14. # build rules from all web page with `dynamic_route = 1`
  15. for d in frappe.get_all(
  16. "Web Page", fields=["name", "route", "modified"], filters=dict(published=1, dynamic_route=1)
  17. ):
  18. rules.append(Rule("/" + d.route, endpoint=d.name))
  19. d.doctype = "Web Page"
  20. page_info[d.name] = d
  21. end_point = evaluate_dynamic_routes(rules, path)
  22. if end_point:
  23. return page_info[end_point]
  24. def get_page_info_from_web_form(path):
  25. """Query published web forms and evaluate if the route matches"""
  26. rules, page_info = [], {}
  27. web_forms = frappe.get_all("Web Form", ["name", "route", "modified"], {"published": 1})
  28. for d in web_forms:
  29. rules.append(Rule(f"/{d.route}", endpoint=d.name))
  30. rules.append(Rule(f"/{d.route}/list", endpoint=d.name))
  31. rules.append(Rule(f"/{d.route}/new", endpoint=d.name))
  32. rules.append(Rule(f"/{d.route}/<name>", endpoint=d.name))
  33. rules.append(Rule(f"/{d.route}/<name>/edit", endpoint=d.name))
  34. d.doctype = "Web Form"
  35. page_info[d.name] = d
  36. end_point = evaluate_dynamic_routes(rules, path)
  37. if end_point:
  38. if path.endswith("/list"):
  39. frappe.form_dict.is_list = True
  40. elif path.endswith("/new"):
  41. frappe.form_dict.is_new = True
  42. elif path.endswith("/edit"):
  43. frappe.form_dict.is_edit = True
  44. else:
  45. frappe.form_dict.is_read = True
  46. return page_info[end_point]
  47. def evaluate_dynamic_routes(rules, path):
  48. """
  49. Use Werkzeug routing to evaluate dynamic routes like /project/<name>
  50. https://werkzeug.palletsprojects.com/en/1.0.x/routing/
  51. """
  52. route_map = Map(rules)
  53. endpoint = None
  54. if hasattr(frappe.local, "request") and frappe.local.request.environ:
  55. urls = route_map.bind_to_environ(frappe.local.request.environ)
  56. try:
  57. endpoint, args = urls.match("/" + path)
  58. path = endpoint
  59. if args:
  60. # don't cache when there's a query string!
  61. frappe.local.no_cache = 1
  62. frappe.local.form_dict.update(args)
  63. except NotFound:
  64. pass
  65. return endpoint
  66. def get_pages(app=None):
  67. """Get all pages. Called for docs / sitemap"""
  68. def _build(app):
  69. pages = {}
  70. if app:
  71. apps = [app]
  72. else:
  73. apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps()
  74. for app in apps:
  75. app_path = frappe.get_app_path(app)
  76. for start in get_start_folders():
  77. pages.update(get_pages_from_path(start, app, app_path))
  78. return pages
  79. return frappe.cache().get_value("website_pages", lambda: _build(app))
  80. def get_pages_from_path(start, app, app_path):
  81. pages = {}
  82. start_path = os.path.join(app_path, start)
  83. if os.path.exists(start_path):
  84. for basepath, folders, files in os.walk(start_path):
  85. # add missing __init__.py
  86. if not "__init__.py" in files:
  87. open(os.path.join(basepath, "__init__.py"), "a").close()
  88. for fname in files:
  89. fname = frappe.utils.cstr(fname)
  90. if not "." in fname:
  91. continue
  92. page_name, extn = fname.rsplit(".", 1)
  93. if extn in ("js", "css") and os.path.exists(os.path.join(basepath, page_name + ".html")):
  94. # js, css is linked to html, skip
  95. continue
  96. if extn in ("html", "xml", "js", "css", "md"):
  97. page_info = get_page_info(
  98. os.path.join(basepath, fname), app, start, basepath, app_path, fname
  99. )
  100. pages[page_info.route] = page_info
  101. # print frappe.as_json(pages[-1])
  102. return pages
  103. def get_page_info(path, app, start, basepath=None, app_path=None, fname=None):
  104. """Load page info"""
  105. if fname is None:
  106. fname = os.path.basename(path)
  107. if app_path is None:
  108. app_path = frappe.get_app_path(app)
  109. if basepath is None:
  110. basepath = os.path.dirname(path)
  111. page_name, extn = os.path.splitext(fname)
  112. # add website route
  113. page_info = frappe._dict()
  114. page_info.basename = page_name if extn in ("html", "md") else fname
  115. page_info.basepath = basepath
  116. page_info.page_or_generator = "Page"
  117. page_info.template = os.path.relpath(os.path.join(basepath, fname), app_path)
  118. if page_info.basename == "index":
  119. page_info.basename = ""
  120. # get route from template name
  121. page_info.route = page_info.template.replace(start, "").strip("/")
  122. if os.path.basename(page_info.route) in ("index.html", "index.md"):
  123. page_info.route = os.path.dirname(page_info.route)
  124. # remove the extension
  125. if page_info.route.endswith(".md") or page_info.route.endswith(".html"):
  126. page_info.route = page_info.route.rsplit(".", 1)[0]
  127. page_info.name = page_info.page_name = page_info.route
  128. # controller
  129. page_info.controller_path = os.path.join(basepath, page_name.replace("-", "_") + ".py")
  130. if os.path.exists(page_info.controller_path):
  131. controller = (
  132. app + "." + os.path.relpath(page_info.controller_path, app_path).replace(os.path.sep, ".")[:-3]
  133. )
  134. page_info.controller = controller
  135. # get the source
  136. setup_source(page_info)
  137. if not page_info.title:
  138. page_info.title = extract_title(page_info.source, page_info.route)
  139. # extract properties from controller attributes
  140. load_properties_from_controller(page_info)
  141. return page_info
  142. def setup_source(page_info):
  143. """Get the HTML source of the template"""
  144. jenv = frappe.get_jenv()
  145. source = jenv.loader.get_source(jenv, page_info.template)[0]
  146. html = ""
  147. if page_info.template.endswith((".md", ".html")):
  148. # extract frontmatter block if exists
  149. try:
  150. # values will be used to update page_info
  151. res = get_frontmatter(source)
  152. if res["attributes"]:
  153. page_info.update(res["attributes"])
  154. source = res["body"]
  155. except Exception:
  156. pass
  157. if page_info.template.endswith(".md"):
  158. source = frappe.utils.md_to_html(source)
  159. page_info.page_toc_html = source.toc_html
  160. if not page_info.show_sidebar:
  161. source = '<div class="from-markdown">' + source + "</div>"
  162. if not page_info.base_template:
  163. page_info.base_template = get_base_template(page_info.route)
  164. if (
  165. page_info.template.endswith(
  166. (
  167. ".html",
  168. ".md",
  169. )
  170. )
  171. and "{%- extends" not in source
  172. and "{% extends" not in source
  173. ):
  174. # set the source only if it contains raw content
  175. html = source
  176. # load css/js files
  177. js_path = os.path.join(page_info.basepath, (page_info.basename or "index") + ".js")
  178. if os.path.exists(js_path) and "{% block script %}" not in html:
  179. with open(js_path, encoding="utf-8") as f:
  180. js = f.read()
  181. page_info.colocated_js = js
  182. css_path = os.path.join(page_info.basepath, (page_info.basename or "index") + ".css")
  183. if os.path.exists(css_path) and "{% block style %}" not in html:
  184. with open(css_path, encoding="utf-8") as f:
  185. css = f.read()
  186. page_info.colocated_css = css
  187. if html:
  188. page_info.source = html
  189. page_info.base_template = page_info.base_template or "templates/web.html"
  190. else:
  191. page_info.source = ""
  192. # show table of contents
  193. setup_index(page_info)
  194. def get_base_template(path=None):
  195. """
  196. Returns the `base_template` for given `path`.
  197. The default `base_template` for any web route is `templates/web.html` defined in `hooks.py`.
  198. This can be overridden for certain routes in `custom_app/hooks.py` based on regex pattern.
  199. """
  200. if not path:
  201. path = frappe.local.request.path
  202. base_template_map = frappe.get_hooks("base_template_map") or {}
  203. patterns = list(base_template_map.keys())
  204. patterns_desc = sorted(patterns, key=lambda x: len(x), reverse=True)
  205. for pattern in patterns_desc:
  206. if re.match(pattern, path):
  207. templates = base_template_map[pattern]
  208. base_template = templates[-1]
  209. return base_template
  210. def setup_index(page_info):
  211. """Build page sequence from index.txt"""
  212. if page_info.basename == "":
  213. # load index.txt if loading all pages
  214. index_txt_path = os.path.join(page_info.basepath, "index.txt")
  215. if os.path.exists(index_txt_path):
  216. with open(index_txt_path) as f:
  217. page_info.index = f.read().splitlines()
  218. def load_properties_from_controller(page_info):
  219. if not page_info.controller:
  220. return
  221. module = frappe.get_module(page_info.controller)
  222. if not module:
  223. return
  224. for prop in ("base_template_path", "template", "no_cache", "sitemap", "condition_field"):
  225. if hasattr(module, prop):
  226. page_info[prop] = getattr(module, prop)
  227. def get_doctypes_with_web_view():
  228. """Return doctypes with Has Web View or set via hooks"""
  229. def _get():
  230. installed_apps = frappe.get_installed_apps()
  231. doctypes = frappe.get_hooks("website_generators")
  232. doctypes_with_web_view = frappe.get_all(
  233. "DocType", fields=["name", "module"], filters=dict(has_web_view=1)
  234. )
  235. module_app_map = frappe.local.module_app
  236. doctypes += [
  237. d.name
  238. for d in doctypes_with_web_view
  239. if module_app_map.get(frappe.scrub(d.module)) in installed_apps
  240. ]
  241. return doctypes
  242. return frappe.cache().get_value("doctypes_with_web_view", _get)
  243. def get_start_folders():
  244. return frappe.local.flags.web_pages_folders or ("www", "templates/pages")