You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

332 line
10 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe, os
  5. from frappe.website.utils import can_cache, delete_page_cache
  6. from frappe.model.document import get_controller
  7. from frappe import _
  8. def get_page_context(path):
  9. page_context = None
  10. if can_cache():
  11. page_context_cache = frappe.cache().hget("page_context", path) or {}
  12. page_context = page_context_cache.get(frappe.local.lang, None)
  13. if not page_context:
  14. page_context = make_page_context(path)
  15. if can_cache(page_context.no_cache):
  16. page_context_cache[frappe.local.lang] = page_context
  17. frappe.cache().hset("page_context", path, page_context_cache)
  18. return page_context
  19. def make_page_context(path):
  20. context = resolve_route(path)
  21. if not context:
  22. raise frappe.DoesNotExistError
  23. context.doctype = context.ref_doctype
  24. context.title = context.page_title
  25. context.pathname = frappe.local.path
  26. return context
  27. def resolve_route(path):
  28. """Returns the page route object based on searching in pages and generators.
  29. The `www` folder is also a part of generator **Web Page**.
  30. The only exceptions are `/about` and `/contact` these will be searched in Web Pages
  31. first before checking the standard pages."""
  32. if path not in ("about", "contact"):
  33. context = get_page_context_from_template(path)
  34. if context:
  35. return context
  36. return get_page_context_from_doctype(path)
  37. else:
  38. context = get_page_context_from_doctype(path)
  39. if context:
  40. return context
  41. return get_page_context_from_template(path)
  42. def get_page_context_from_template(path):
  43. '''Return page_info from path'''
  44. for app in frappe.get_installed_apps():
  45. app_path = frappe.get_app_path(app)
  46. folders = frappe.local.flags.web_pages_folders or ('www', 'templates/pages')
  47. for start in folders:
  48. search_path = os.path.join(app_path, start, path)
  49. options = (search_path, search_path + '.html', search_path + '.md',
  50. search_path + '/index.html', search_path + '/index.md')
  51. for o in options:
  52. if os.path.exists(o) and not os.path.isdir(o):
  53. return get_page_info(o, app, app_path=app_path)
  54. return None
  55. def get_page_context_from_doctype(path):
  56. page_info = get_page_info_from_doctypes(path)
  57. if page_info:
  58. return frappe.get_doc(page_info.get("doctype"), page_info.get("name")).get_page_info()
  59. def clear_sitemap():
  60. delete_page_cache("*")
  61. def get_all_page_context_from_doctypes():
  62. '''Get all doctype generated routes (for sitemap.xml)'''
  63. routes = frappe.cache().get_value("website_generator_routes")
  64. if not routes:
  65. routes = get_page_info_from_doctypes()
  66. frappe.cache().set_value("website_generator_routes", routes)
  67. return routes
  68. def get_page_info_from_doctypes(path=None):
  69. routes = {}
  70. for app in frappe.get_installed_apps():
  71. for doctype in frappe.get_hooks("website_generators", app_name = app):
  72. condition = ""
  73. values = []
  74. controller = get_controller(doctype)
  75. if controller.website.condition_field:
  76. condition ="where {0}=1".format(controller.website.condition_field)
  77. if path:
  78. condition += ' {0} `route`=%s limit 1'.format('and' if 'where' in condition else 'where')
  79. values.append(path)
  80. for r in frappe.db.sql("""select route, name, modified from `tab{0}`
  81. {1}""".format(doctype, condition), values=values, as_dict=True):
  82. routes[r.route] = {"doctype": doctype, "name": r.name, "modified": r.modified}
  83. # just want one path, return it!
  84. if path:
  85. return routes[r.route]
  86. return routes
  87. def get_pages(app=None):
  88. '''Get all pages. Called for docs / sitemap'''
  89. pages = {}
  90. frappe.local.flags.in_get_all_pages = True
  91. folders = frappe.local.flags.web_pages_folders or ('www', 'templates/pages')
  92. if app:
  93. apps = [app]
  94. else:
  95. apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps()
  96. for app in apps:
  97. app_path = frappe.get_app_path(app)
  98. for start in folders:
  99. path = os.path.join(app_path, start)
  100. pages.update(get_pages_from_path(path, app, app_path))
  101. frappe.local.flags.in_get_all_pages = False
  102. return pages
  103. def get_pages_from_path(path, app, app_path):
  104. pages = {}
  105. if os.path.exists(path):
  106. for basepath, folders, files in os.walk(path):
  107. # add missing __init__.py
  108. if not '__init__.py' in files:
  109. open(os.path.join(basepath, '__init__.py'), 'a').close()
  110. for fname in files:
  111. fname = frappe.utils.cstr(fname)
  112. page_name, extn = fname.rsplit(".", 1)
  113. if extn in ('js', 'css') and os.path.exists(os.path.join(basepath, fname + '.html')):
  114. # js, css is linked to html, skip
  115. continue
  116. if extn in ("html", "xml", "js", "css", "md"):
  117. page_info = get_page_info(path, app, basepath, app_path, fname)
  118. pages[page_info.route] = page_info
  119. # print frappe.as_json(pages[-1])
  120. return pages
  121. def get_page_info(path, app, basepath=None, app_path=None, fname=None):
  122. '''Load page info'''
  123. if not fname:
  124. fname = os.path.basename(path)
  125. if not app_path:
  126. app_path = frappe.get_app_path(app)
  127. if not basepath:
  128. basepath = os.path.dirname(path)
  129. page_name, extn = fname.rsplit(".", 1)
  130. # add website route
  131. page_info = frappe._dict()
  132. page_info.basename = page_name if extn in ('html', 'md') else fname
  133. page_info.basepath = basepath
  134. page_info.page_or_generator = "Page"
  135. page_info.template = os.path.relpath(os.path.join(basepath, fname), app_path)
  136. if page_info.basename == 'index':
  137. page_info.basename = ""
  138. page_info.route = page_info.name = page_info.page_name = os.path.join(os.path.relpath(basepath, path),
  139. page_info.basename).strip('/.')
  140. # controller
  141. page_info.controller_path = os.path.join(basepath, page_name.replace("-", "_") + ".py")
  142. if os.path.exists(page_info.controller_path):
  143. controller = app + "." + os.path.relpath(page_info.controller_path,
  144. app_path).replace(os.path.sep, ".")[:-3]
  145. page_info.controller = controller
  146. # get the source
  147. setup_source(page_info)
  148. if page_info.only_content:
  149. # extract properties from HTML comments
  150. load_properties(page_info)
  151. return page_info
  152. def setup_source(page_info):
  153. '''Get the HTML source of the template'''
  154. from markdown2 import markdown
  155. jenv = frappe.get_jenv()
  156. source = jenv.loader.get_source(jenv, page_info.template)[0]
  157. html = ''
  158. if page_info.template.endswith('.md'):
  159. source = markdown(source)
  160. # if only content
  161. if page_info.template.endswith('.html') or page_info.template.endswith('.md'):
  162. if ('</body>' not in source) and ('{% block' not in source):
  163. page_info.only_content = True
  164. js, css = '', ''
  165. js_path = os.path.join(page_info.basepath, page_info.basename + '.js')
  166. if os.path.exists(js_path):
  167. js = unicode(open(js_path, 'r').read(), 'utf-8')
  168. css_path = os.path.join(page_info.basepath, page_info.basename + '.css')
  169. if os.path.exists(css_path):
  170. css = unicode(open(css_path, 'r').read(), 'utf-8')
  171. html = '{% extends "templates/web.html" %}'
  172. if css:
  173. html += '\n{% block style %}\n<style>\n' + css + '\n</style>\n{% endblock %}'
  174. html += '\n{% block page_content %}\n' + source + '\n{% endblock %}'
  175. if js:
  176. html += '\n{% block script %}<script>' + js + '\n</script>\n{% endblock %}'
  177. else:
  178. html = source
  179. page_info.source = html
  180. # show table of contents
  181. setup_index(page_info)
  182. def setup_index(page_info):
  183. '''Build page sequence from index.txt'''
  184. if page_info.basename=='':
  185. # load index.txt if loading all pages
  186. index_txt_path = os.path.join(page_info.basepath, 'index.txt')
  187. if os.path.exists(index_txt_path):
  188. page_info.index = open(index_txt_path, 'r').read().splitlines()
  189. def make_toc(context, out):
  190. '''Insert full index (table of contents) for {index} tag'''
  191. from frappe.website.utils import get_full_index
  192. if '{index}' in out:
  193. html = frappe.get_template("templates/includes/full_index.html").render({
  194. "full_index": get_full_index(),
  195. "url_prefix": context.url_prefix or "/",
  196. "route": context.route
  197. })
  198. out = out.replace('{index}', html)
  199. if '{next}' in out:
  200. # insert next link
  201. next_item = None
  202. children_map = get_full_index()
  203. parent_route = os.path.dirname(context.route)
  204. children = children_map[parent_route]
  205. if parent_route and children:
  206. for i, c in enumerate(children):
  207. if c.route == context.route and i < (len(children) - 1):
  208. next_item = children[i+1]
  209. next_item.url_prefix = context.url_prefix or "/"
  210. if next_item:
  211. if next_item.route and next_item.title:
  212. html = ('<p class="btn-next-wrapper">'+_("Next")\
  213. +': <a class="btn-next" href="{url_prefix}{route}">{title}</a></p>').format(**next_item)
  214. out = out.replace('{next}', html)
  215. return out
  216. def load_properties(page_info):
  217. '''Load properties like no_cache, title from raw'''
  218. import re
  219. if not page_info.title:
  220. if "<!-- title:" in page_info.source:
  221. page_info.title = re.findall('<!-- title:([^>]*) -->', page_info.source)[0].strip()
  222. elif "<h1>" in page_info.source:
  223. match = re.findall('<h1>([^>]*)</h1>', page_info.source)
  224. if match:
  225. page_info.title = match[0].strip()
  226. else:
  227. page_info.title = os.path.basename(page_info.name).replace('_', ' ').replace('-', ' ').title()
  228. if page_info.title and not '{% block title %}' in page_info.source:
  229. page_info.source += '\n{% block title %}' + page_info.title + '{% endblock %}'
  230. if "<!-- no-breadcrumbs -->" in page_info.source:
  231. page_info.no_breadcrumbs = 1
  232. if "<!-- no-header -->" in page_info.source:
  233. page_info.no_header = 1
  234. else:
  235. # every page needs a header
  236. # add missing header if there is no <h1> tag
  237. if (not '{% block header %}' in page_info.source) and (not '<h1' in page_info.source):
  238. page_info.source += '\n{% block header %}<h1>' + page_info.title + '</h1>{% endblock %}'
  239. if "<!-- no-cache -->" in page_info.source:
  240. page_info.no_cache = 1
  241. def process_generators(func):
  242. for app in frappe.get_installed_apps():
  243. for doctype in frappe.get_hooks("website_generators", app_name = app):
  244. order_by = "name asc"
  245. condition_field = None
  246. controller = get_controller(doctype)
  247. if hasattr(controller, "condition_field"):
  248. condition_field = controller.condition_field
  249. if hasattr(controller, "order_by"):
  250. order_by = controller.order_by
  251. val = func(doctype, condition_field, order_by)
  252. if val:
  253. return val