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.
 
 
 
 
 
 

313 lines
8.2 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
  5. from frappe import _
  6. import frappe.sessions
  7. from frappe.utils import cstr
  8. import os, mimetypes, json
  9. from six import iteritems
  10. from werkzeug.wrappers import Response
  11. from werkzeug.routing import Map, Rule, NotFound
  12. from werkzeug.wsgi import wrap_file
  13. from frappe.website.context import get_context
  14. from frappe.website.utils import (get_home_page, can_cache, delete_page_cache,
  15. get_toc, get_next_link)
  16. from frappe.website.router import clear_sitemap
  17. from frappe.translate import guess_language
  18. class PageNotFoundError(Exception): pass
  19. def render(path=None, http_status_code=None):
  20. """render html page"""
  21. if not path:
  22. path = frappe.local.request.path
  23. path = resolve_path(path.strip('/ '))
  24. data = None
  25. # if in list of already known 404s, send it
  26. if can_cache() and frappe.cache().hget('website_404', frappe.request.url):
  27. data = render_page('404')
  28. http_status_code = 404
  29. elif is_static_file(path):
  30. return get_static_file_response()
  31. else:
  32. try:
  33. data = render_page_by_language(path)
  34. except frappe.DoesNotExistError as e:
  35. doctype, name = get_doctype_from_path(path)
  36. if doctype and name:
  37. path = "printview"
  38. frappe.local.form_dict.doctype = doctype
  39. frappe.local.form_dict.name = name
  40. elif doctype:
  41. path = "list"
  42. frappe.local.form_dict.doctype = doctype
  43. else:
  44. # 404s are expensive, cache them!
  45. frappe.cache().hset('website_404', frappe.request.url, True)
  46. data = render_page('404')
  47. http_status_code = 404
  48. if not data:
  49. try:
  50. data = render_page(path)
  51. except frappe.PermissionError as e:
  52. data, http_status_code = render_403(e, path)
  53. except frappe.PermissionError as e:
  54. data, http_status_code = render_403(e, path)
  55. except frappe.Redirect as e:
  56. return build_response(path, "", 301, {
  57. "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'),
  58. "Cache-Control": "no-store, no-cache, must-revalidate"
  59. })
  60. except Exception:
  61. path = "error"
  62. data = render_page(path)
  63. http_status_code = 500
  64. data = add_csrf_token(data)
  65. return build_response(path, data, http_status_code or 200)
  66. def is_static_file(path):
  67. if ('.' not in path):
  68. return False
  69. extn = path.rsplit('.', 1)[-1]
  70. if extn in ('html', 'md', 'js', 'xml', 'css', 'txt', 'py'):
  71. return False
  72. for app in frappe.get_installed_apps():
  73. file_path = frappe.get_app_path(app, 'www') + '/' + path
  74. if os.path.exists(file_path):
  75. frappe.flags.file_path = file_path
  76. return True
  77. return False
  78. def get_static_file_response():
  79. try:
  80. f = open(frappe.flags.file_path, 'rb')
  81. except IOError:
  82. raise NotFound
  83. response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True)
  84. response.mimetype = mimetypes.guess_type(frappe.flags.file_path)[0] or b'application/octet-stream'
  85. return response
  86. def build_response(path, data, http_status_code, headers=None):
  87. # build response
  88. response = Response()
  89. response.data = set_content_type(response, data, path)
  90. response.status_code = http_status_code
  91. response.headers[b"X-Page-Name"] = path.encode("utf-8")
  92. response.headers[b"X-From-Cache"] = frappe.local.response.from_cache or False
  93. if headers:
  94. for key, val in iteritems(headers):
  95. response.headers[bytes(key)] = val.encode("utf-8")
  96. return response
  97. def render_page_by_language(path):
  98. translated_languages = frappe.get_hooks("translated_languages_for_website")
  99. user_lang = guess_language(translated_languages)
  100. if translated_languages and user_lang in translated_languages:
  101. try:
  102. if path and path != "index":
  103. lang_path = '{0}/{1}'.format(user_lang, path)
  104. else:
  105. lang_path = user_lang # index
  106. return render_page(lang_path)
  107. except frappe.DoesNotExistError:
  108. return render_page(path)
  109. else:
  110. return render_page(path)
  111. def render_page(path):
  112. """get page html"""
  113. out = None
  114. if can_cache():
  115. # return rendered page
  116. page_cache = frappe.cache().hget("website_page", path)
  117. if page_cache and frappe.local.lang in page_cache:
  118. out = page_cache[frappe.local.lang]
  119. if out:
  120. frappe.local.response.from_cache = True
  121. return out
  122. return build(path)
  123. def build(path):
  124. if not frappe.db:
  125. frappe.connect()
  126. try:
  127. return build_page(path)
  128. except frappe.DoesNotExistError:
  129. hooks = frappe.get_hooks()
  130. if hooks.website_catch_all:
  131. path = hooks.website_catch_all[0]
  132. return build_page(path)
  133. else:
  134. raise
  135. def build_page(path):
  136. if not getattr(frappe.local, "path", None):
  137. frappe.local.path = path
  138. context = get_context(path)
  139. if context.title and "{{" in context.title:
  140. title_template = context.pop('title')
  141. context.title = frappe.render_template(title_template, context)
  142. if context.source:
  143. html = frappe.render_template(context.source, context)
  144. elif context.template:
  145. html = frappe.get_template(context.template).render(context)
  146. if '{index}' in html:
  147. html = html.replace('{index}', get_toc(context.route))
  148. if '{next}' in html:
  149. html = html.replace('{next}', get_next_link(context.route))
  150. # html = frappe.get_template(context.base_template_path).render(context)
  151. if can_cache(context.no_cache):
  152. page_cache = frappe.cache().hget("website_page", path) or {}
  153. page_cache[frappe.local.lang] = html
  154. frappe.cache().hset("website_page", path, page_cache)
  155. return html
  156. def resolve_path(path):
  157. if not path:
  158. path = "index"
  159. if path.endswith('.html'):
  160. path = path[:-5]
  161. if path == "index":
  162. path = get_home_page()
  163. frappe.local.path = path
  164. if path != "index":
  165. path = resolve_from_map(path)
  166. return path
  167. def resolve_from_map(path):
  168. m = Map([Rule(r["from_route"], endpoint=r["to_route"], defaults=r.get("defaults"))
  169. for r in get_website_rules()])
  170. urls = m.bind_to_environ(frappe.local.request.environ)
  171. try:
  172. endpoint, args = urls.match("/" + path)
  173. path = endpoint
  174. if args:
  175. # don't cache when there's a query string!
  176. frappe.local.no_cache = 1
  177. frappe.local.form_dict.update(args)
  178. except NotFound:
  179. pass
  180. return path
  181. def get_website_rules():
  182. '''Get website route rules from hooks and DocType route'''
  183. def _get():
  184. rules = frappe.get_hooks("website_route_rules")
  185. for d in frappe.get_all('DocType', 'name, route', dict(has_web_view=1)):
  186. if d.route:
  187. rules.append(dict(from_route = '/' + d.route.strip('/'), to_route=d.name))
  188. return rules
  189. return frappe.cache().get_value('website_route_rules', _get)
  190. def set_content_type(response, data, path):
  191. if isinstance(data, dict):
  192. response.mimetype = 'application/json'
  193. response.charset = 'utf-8'
  194. data = json.dumps(data)
  195. return data
  196. response.mimetype = 'text/html'
  197. response.charset = 'utf-8'
  198. if "." in path:
  199. content_type, encoding = mimetypes.guess_type(path)
  200. if content_type:
  201. response.mimetype = content_type
  202. if encoding:
  203. response.charset = encoding
  204. return data
  205. def clear_cache(path=None):
  206. '''Clear website caches
  207. :param path: (optional) for the given path'''
  208. for key in ('website_generator_routes', 'website_pages',
  209. 'website_full_index'):
  210. frappe.cache().delete_value(key)
  211. delete_page_cache(path)
  212. frappe.cache().delete_value("website_404")
  213. if not path:
  214. clear_sitemap()
  215. frappe.clear_cache("Guest")
  216. for key in ('portal_menu_items', 'home_page', 'website_route_rules',
  217. 'doctypes_with_web_view'):
  218. frappe.cache().delete_value(key)
  219. for method in frappe.get_hooks("website_clear_cache"):
  220. frappe.get_attr(method)(path)
  221. def render_403(e, pathname):
  222. frappe.local.message = cstr(e.message)
  223. frappe.local.message_title = _("Not Permitted")
  224. frappe.local.response['context'] = dict(
  225. indicator_color = 'red',
  226. primary_action = '/login',
  227. primary_label = _('Login'),
  228. fullpage=True
  229. )
  230. return render_page("message"), e.http_status_code
  231. def get_doctype_from_path(path):
  232. doctypes = frappe.db.sql_list("select name from tabDocType")
  233. parts = path.split("/")
  234. doctype = parts[0]
  235. name = parts[1] if len(parts) > 1 else None
  236. if doctype in doctypes:
  237. return doctype, name
  238. # try scrubbed
  239. doctype = doctype.replace("_", " ").title()
  240. if doctype in doctypes:
  241. return doctype, name
  242. return None, None
  243. def add_csrf_token(data):
  244. if frappe.local.session:
  245. return data.replace("<!-- csrf_token -->", '<script>frappe.csrf_token = "{0}";</script>'.format(
  246. frappe.local.session.data.csrf_token))
  247. else:
  248. return data