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.

webutils.py 14 KiB

11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
12 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
11 vuotta sitten
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import webnotes
  5. import json, os, time, re
  6. from webnotes import _
  7. import webnotes.utils
  8. from webnotes.utils import get_request_site_address, encode, cint
  9. from webnotes.model import default_fields
  10. from webnotes.model.controller import DocListController
  11. from urllib import quote
  12. import mimetypes
  13. from webnotes.website.doctype.website_sitemap.website_sitemap import add_to_sitemap, update_sitemap, remove_sitemap
  14. # frequently used imports (used by other modules)
  15. from webnotes.website.doctype.website_sitemap_permission.website_sitemap_permission \
  16. import get_access, clear_permissions
  17. class PageNotFoundError(Exception): pass
  18. def render(path):
  19. """render html page"""
  20. path = resolve_path(path)
  21. try:
  22. data = render_page(path)
  23. except Exception:
  24. path = "error"
  25. data = render_page(path)
  26. data = set_content_type(data, path)
  27. webnotes._response.data = data
  28. webnotes._response.headers[b"Page Name"] = path.encode("utf-8")
  29. def render_page(path):
  30. """get page html"""
  31. cache_key = ("page_context:{}" if is_ajax() else "page:{}").format(path)
  32. out = None
  33. # try memcache
  34. if can_cache():
  35. out = webnotes.cache().get_value(cache_key)
  36. if out and is_ajax():
  37. out = out.get("data")
  38. if out:
  39. if hasattr(webnotes, "_response"):
  40. webnotes._response.headers[b"From Cache"] = True
  41. return out
  42. return build(path)
  43. def build(path):
  44. if not webnotes.conn:
  45. webnotes.connect()
  46. build_method = (build_json if is_ajax() else build_page)
  47. try:
  48. return build_method(path)
  49. except webnotes.DoesNotExistError:
  50. hooks = webnotes.get_hooks()
  51. if hooks.website_catch_all:
  52. return build_method(hooks.website_catch_all[0])
  53. else:
  54. return build_method("404")
  55. def build_json(path):
  56. return get_context(path).data
  57. def build_page(path):
  58. context = get_context(path)
  59. html = webnotes.get_template(context.base_template_path).render(context)
  60. html = scrub_relative_urls(html)
  61. if can_cache(context.no_cache):
  62. webnotes.cache().set_value("page:" + path, html)
  63. return html
  64. def get_context(path):
  65. context = None
  66. cache_key = "page_context:{}".format(path)
  67. from pickle import dump
  68. from StringIO import StringIO
  69. # try from memcache
  70. if can_cache():
  71. context = webnotes.cache().get_value(cache_key)
  72. if not context:
  73. context = get_sitemap_options(path)
  74. # permission may be required for rendering
  75. context["access"] = get_access(context.pathname)
  76. context = build_context(context)
  77. if can_cache(context.no_cache):
  78. webnotes.cache().set_value(cache_key, context)
  79. else:
  80. context["access"] = get_access(context.pathname)
  81. context.update(context.data or {})
  82. # TODO private pages
  83. return context
  84. def get_sitemap_options(path):
  85. sitemap_options = None
  86. cache_key = "sitemap_options:{}".format(path)
  87. if can_cache():
  88. sitemap_options = webnotes._dict(webnotes.cache().get_value(cache_key))
  89. if not sitemap_options:
  90. sitemap_options = build_sitemap_options(path)
  91. if can_cache(sitemap_options.no_cache):
  92. webnotes.cache().set_value(cache_key, sitemap_options)
  93. return sitemap_options
  94. def build_sitemap_options(path):
  95. sitemap_options = webnotes.doc("Website Sitemap", path).fields
  96. home_page = get_home_page()
  97. sitemap_config = webnotes.doc("Website Sitemap Config",
  98. sitemap_options.get("website_sitemap_config")).fields
  99. # get sitemap config fields too
  100. for fieldname in ("base_template_path", "template_path", "controller", "no_cache", "no_sitemap",
  101. "page_name_field", "condition_field"):
  102. sitemap_options[fieldname] = sitemap_config.get(fieldname)
  103. sitemap_options.doctype = sitemap_options.ref_doctype
  104. sitemap_options.title = sitemap_options.page_title
  105. sitemap_options.pathname = sitemap_options.name
  106. def set_sidebar_items(pathname):
  107. if pathname==home_page or not pathname:
  108. sitemap_options.children = webnotes.conn.sql("""select url as name, label as page_title,
  109. 1 as public_read from `tabTop Bar Item` where parentfield='sidebar_items' order by idx""", as_dict=True)
  110. else:
  111. sitemap_options.children = webnotes.conn.sql("""select * from `tabWebsite Sitemap`
  112. where ifnull(parent_website_sitemap,'')=%s
  113. and public_read=1 order by -idx desc, page_title asc""", pathname, as_dict=True)
  114. # establish hierarchy
  115. sitemap_options.parents = webnotes.conn.sql("""select name, page_title from `tabWebsite Sitemap`
  116. where lft < %s and rgt > %s order by lft asc""", (sitemap_options.lft, sitemap_options.rgt), as_dict=True)
  117. if not sitemap_options.no_sidebar:
  118. set_sidebar_items(sitemap_options.pathname)
  119. if not sitemap_options.children:
  120. set_sidebar_items(sitemap_options.parent_website_sitemap)
  121. # determine templates to be used
  122. if not sitemap_options.base_template_path:
  123. sitemap_options.base_template_path = "templates/base.html"
  124. return sitemap_options
  125. def build_context(sitemap_options):
  126. """get_context method of bean or module is supposed to render content templates and push it into context"""
  127. context = webnotes._dict(sitemap_options)
  128. context.update(get_website_settings())
  129. # provide bean
  130. if context.doctype and context.docname:
  131. context.bean = webnotes.bean(context.doctype, context.docname)
  132. if context.controller:
  133. module = webnotes.get_module(context.controller)
  134. if module and hasattr(module, "get_context"):
  135. context.update(module.get_context(context) or {})
  136. if context.get("base_template_path") != context.get("template_path") and not context.get("rendered"):
  137. context.data = render_blocks(context)
  138. # remove bean, as it is not pickle friendly and its purpose is over
  139. if context.bean:
  140. del context["bean"]
  141. return context
  142. def can_cache(no_cache=False):
  143. return not (webnotes.conf.disable_website_cache or no_cache)
  144. def get_home_page():
  145. home_page = webnotes.cache().get_value("home_page", \
  146. lambda: (webnotes.get_hooks("home_page") \
  147. or [webnotes.conn.get_value("Website Settings", None, "home_page") \
  148. or "login"])[0])
  149. return home_page
  150. def get_website_settings():
  151. # TODO Cache this
  152. hooks = webnotes.get_hooks()
  153. all_top_items = webnotes.conn.sql("""\
  154. select * from `tabTop Bar Item`
  155. where parent='Website Settings' and parentfield='top_bar_items'
  156. order by idx asc""", as_dict=1)
  157. top_items = [d for d in all_top_items if not d['parent_label']]
  158. # attach child items to top bar
  159. for d in all_top_items:
  160. if d['parent_label']:
  161. for t in top_items:
  162. if t['label']==d['parent_label']:
  163. if not 'child_items' in t:
  164. t['child_items'] = []
  165. t['child_items'].append(d)
  166. break
  167. context = webnotes._dict({
  168. 'top_bar_items': top_items,
  169. 'footer_items': webnotes.conn.sql("""\
  170. select * from `tabTop Bar Item`
  171. where parent='Website Settings' and parentfield='footer_items'
  172. order by idx asc""", as_dict=1),
  173. "post_login": [
  174. {"label": "Reset Password", "url": "update-password", "icon": "icon-key"},
  175. {"label": "Logout", "url": "?cmd=web_logout", "icon": "icon-signout"}
  176. ]
  177. })
  178. settings = webnotes.doc("Website Settings", "Website Settings")
  179. for k in ["banner_html", "brand_html", "copyright", "twitter_share_via",
  180. "favicon", "facebook_share", "google_plus_one", "twitter_share", "linked_in_share",
  181. "disable_signup"]:
  182. if k in settings.fields:
  183. context[k] = settings.fields.get(k)
  184. if settings.address:
  185. context["footer_address"] = settings.address
  186. for k in ["facebook_share", "google_plus_one", "twitter_share", "linked_in_share",
  187. "disable_signup"]:
  188. context[k] = cint(context.get(k) or 0)
  189. context.url = quote(str(get_request_site_address(full_address=True)), safe="/:")
  190. context.encoded_title = quote(encode(context.title or ""), str(""))
  191. for update_website_context in hooks.update_website_context or []:
  192. webnotes.get_attr(update_website_context)(context)
  193. context.web_include_js = hooks.web_include_js or []
  194. context.web_include_css = hooks.web_include_css or []
  195. return context
  196. def is_ajax():
  197. return webnotes.get_request_header("X-Requested-With")=="XMLHttpRequest"
  198. def resolve_path(path):
  199. if not path:
  200. path = "index"
  201. if path.endswith('.html'):
  202. path = path[:-5]
  203. if path == "index":
  204. path = get_home_page()
  205. return path
  206. def set_content_type(data, path):
  207. if isinstance(data, dict):
  208. webnotes._response.headers[b"Content-Type"] = b"application/json; charset: utf-8"
  209. data = json.dumps(data)
  210. return data
  211. webnotes._response.headers[b"Content-Type"] = b"text/html; charset: utf-8"
  212. if "." in path and not path.endswith(".html"):
  213. content_type, encoding = mimetypes.guess_type(path)
  214. webnotes._response.headers[b"Content-Type"] = content_type.encode("utf-8")
  215. return data
  216. def clear_cache(path=None):
  217. cache = webnotes.cache()
  218. if path:
  219. delete_page_cache(path)
  220. else:
  221. for p in webnotes.conn.sql_list("""select name from `tabWebsite Sitemap`"""):
  222. if p is not None:
  223. delete_page_cache(p)
  224. cache.delete_value("home_page")
  225. clear_permissions()
  226. for method in webnotes.get_hooks("website_clear_cache"):
  227. webnotes.get_attr(method)(path)
  228. def delete_page_cache(path):
  229. cache = webnotes.cache()
  230. cache.delete_value("page:" + path)
  231. cache.delete_value("page_context:" + path)
  232. cache.delete_value("sitemap_options:" + path)
  233. def is_signup_enabled():
  234. if getattr(webnotes.local, "is_signup_enabled", None) is None:
  235. webnotes.local.is_signup_enabled = True
  236. if webnotes.utils.cint(webnotes.conn.get_value("Website Settings",
  237. "Website Settings", "disable_signup")):
  238. webnotes.local.is_signup_enabled = False
  239. return webnotes.local.is_signup_enabled
  240. def call_website_generator(bean, method, *args, **kwargs):
  241. getattr(WebsiteGenerator(bean.doc, bean.doclist), method)(*args, **kwargs)
  242. class WebsiteGenerator(DocListController):
  243. def autoname(self):
  244. from webnotes.webutils import cleanup_page_name
  245. self.doc.name = cleanup_page_name(self.get_page_title())
  246. def set_page_name(self):
  247. """set page name based on parent page_name and title"""
  248. page_name = cleanup_page_name(self.get_page_title())
  249. if self.doc.is_new():
  250. self.doc.fields[self._website_config.page_name_field] = page_name
  251. else:
  252. webnotes.conn.set(self.doc, self._website_config.page_name_field, page_name)
  253. def setup_generator(self):
  254. self._website_config = webnotes.conn.get_values("Website Sitemap Config",
  255. {"ref_doctype": self.doc.doctype}, "*")[0]
  256. def on_update(self):
  257. self.update_sitemap()
  258. def after_rename(self, olddn, newdn, merge):
  259. webnotes.conn.sql("""update `tabWebsite Sitemap`
  260. set docname=%s where ref_doctype=%s and docname=%s""", (newdn, self.doc.doctype, olddn))
  261. if merge:
  262. self.setup_generator()
  263. remove_sitemap(ref_doctype=self.doc.doctype, docname=olddn)
  264. def on_trash(self):
  265. self.setup_generator()
  266. remove_sitemap(ref_doctype=self.doc.doctype, docname=self.doc.name)
  267. def update_sitemap(self):
  268. self.setup_generator()
  269. if self._website_config.condition_field and \
  270. not self.doc.fields.get(self._website_config.condition_field):
  271. # condition field failed, remove and return!
  272. remove_sitemap(ref_doctype=self.doc.doctype, docname=self.doc.name)
  273. return
  274. self.add_or_update_sitemap()
  275. def add_or_update_sitemap(self):
  276. page_name = self.get_page_name()
  277. existing_site_map = webnotes.conn.get_value("Website Sitemap", {"ref_doctype": self.doc.doctype,
  278. "docname": self.doc.name})
  279. opts = webnotes._dict({
  280. "page_or_generator": "Generator",
  281. "ref_doctype":self.doc.doctype,
  282. "docname": self.doc.name,
  283. "page_name": page_name,
  284. "link_name": self._website_config.name,
  285. "lastmod": webnotes.utils.get_datetime(self.doc.modified).strftime("%Y-%m-%d"),
  286. "parent_website_sitemap": self.doc.parent_website_sitemap,
  287. "page_title": self.get_page_title(),
  288. "public_read": 1 if not self._website_config.no_sidebar else 0
  289. })
  290. self.update_permissions(opts)
  291. if existing_site_map:
  292. update_sitemap(existing_site_map, opts)
  293. else:
  294. add_to_sitemap(opts)
  295. def update_permissions(self, opts):
  296. if self.meta.get_field("public_read"):
  297. opts.public_read = self.doc.public_read
  298. opts.public_write = self.doc.public_write
  299. else:
  300. opts.public_read = 1
  301. def get_page_name(self):
  302. if not self._get_page_name():
  303. self.set_page_name()
  304. return self._get_page_name()
  305. def _get_page_name(self):
  306. return self.doc.fields.get(self._website_config.page_name_field)
  307. def get_page_title(self):
  308. return self.doc.title or (self.doc.name.replace("-", " ").replace("_", " ").title())
  309. def cleanup_page_name(title):
  310. """make page name from title"""
  311. import re
  312. name = title.lower()
  313. name = re.sub('[~!@#$%^&*+()<>,."\'\?]', '', name)
  314. name = re.sub('[:/]', '-', name)
  315. name = '-'.join(name.split())
  316. # replace repeating hyphens
  317. name = re.sub(r"(-)\1+", r"\1", name)
  318. return name
  319. def get_hex_shade(color, percent):
  320. def p(c):
  321. v = int(c, 16) + int(int('ff', 16) * (float(percent)/100))
  322. if v < 0:
  323. v=0
  324. if v > 255:
  325. v=255
  326. h = hex(v)[2:]
  327. if len(h) < 2:
  328. h = "0" + h
  329. return h
  330. r, g, b = color[0:2], color[2:4], color[4:6]
  331. avg = (float(int(r, 16) + int(g, 16) + int(b, 16)) / 3)
  332. # switch dark and light shades
  333. if avg > 128:
  334. percent = -percent
  335. # stronger diff for darker shades
  336. if percent < 25 and avg < 64:
  337. percent = percent * 2
  338. return p(r) + p(g) + p(b)
  339. def render_blocks(context):
  340. """returns a dict of block name and its rendered content"""
  341. from jinja2.utils import concat
  342. from jinja2 import meta
  343. out = {}
  344. env = webnotes.get_jenv()
  345. def _render_blocks(template_path):
  346. source = webnotes.local.jloader.get_source(webnotes.local.jenv, template_path)[0]
  347. for referenced_template_path in meta.find_referenced_templates(env.parse(source)):
  348. if referenced_template_path:
  349. _render_blocks(referenced_template_path)
  350. template = webnotes.get_template(template_path)
  351. for block, render in template.blocks.items():
  352. out[block] = scrub_relative_urls(concat(render(template.new_context(context))))
  353. _render_blocks(context["template_path"])
  354. return out
  355. def scrub_relative_urls(html):
  356. """prepend a slash before a relative url"""
  357. return re.sub("""(src|href)[^\w'"]*['"](?!http|ftp|/|#)([^'" >]+)['"]""", '\g<1> = "/\g<2>"', html)