25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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