No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

439 líneas
12 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, copy, json, re
  5. from frappe import _
  6. from frappe.modules import get_doc_path
  7. from jinja2 import TemplateNotFound
  8. from frappe.utils import cint, strip_html
  9. from markdown2 import markdown
  10. no_cache = 1
  11. no_sitemap = 1
  12. base_template_path = "templates/www/print.html"
  13. standard_format = "templates/print_formats/standard.html"
  14. def get_context(context):
  15. """Build context for print"""
  16. if not ((frappe.form_dict.doctype and frappe.form_dict.name) or frappe.form_dict.doc):
  17. return {
  18. "body": """<h1>Error</h1>
  19. <p>Parameters doctype and name required</p>
  20. <pre>%s</pre>""" % repr(frappe.form_dict)
  21. }
  22. if frappe.form_dict.doc:
  23. doc = frappe.form_dict.doc
  24. else:
  25. doc = frappe.get_doc(frappe.form_dict.doctype, frappe.form_dict.name)
  26. meta = frappe.get_meta(doc.doctype)
  27. print_format = get_print_format_doc(None, meta = meta)
  28. return {
  29. "body": get_html(doc, print_format = print_format,
  30. meta=meta, trigger_print = frappe.form_dict.trigger_print,
  31. no_letterhead=frappe.form_dict.no_letterhead),
  32. "css": get_print_style(frappe.form_dict.style, print_format),
  33. "comment": frappe.session.user,
  34. "title": doc.get(meta.title_field) if meta.title_field else doc.name
  35. }
  36. def get_print_format_doc(print_format_name, meta):
  37. """Returns print format document"""
  38. if not print_format_name:
  39. print_format_name = frappe.form_dict.format \
  40. or meta.default_print_format or "Standard"
  41. if print_format_name == "Standard":
  42. return None
  43. else:
  44. try:
  45. return frappe.get_doc("Print Format", print_format_name)
  46. except frappe.DoesNotExistError:
  47. # if old name, return standard!
  48. return None
  49. def get_html(doc, name=None, print_format=None, meta=None,
  50. no_letterhead=None, trigger_print=False):
  51. print_settings = frappe.db.get_singles_dict("Print Settings")
  52. if isinstance(no_letterhead, basestring):
  53. no_letterhead = cint(no_letterhead)
  54. elif no_letterhead is None:
  55. no_letterhead = not cint(print_settings.with_letterhead)
  56. doc.flags.in_print = True
  57. if not frappe.flags.ignore_print_permissions:
  58. validate_print_permission(doc)
  59. if doc.meta.is_submittable:
  60. if doc.docstatus==0 and not print_settings.allow_print_for_draft:
  61. frappe.throw(_("Not allowed to print draft documents"), frappe.PermissionError)
  62. if doc.docstatus==2 and not print_settings.allow_print_for_cancelled:
  63. frappe.throw(_("Not allowed to print cancelled documents"), frappe.PermissionError)
  64. if hasattr(doc, "before_print"):
  65. doc.before_print()
  66. if not hasattr(doc, "print_heading"): doc.print_heading = None
  67. if not hasattr(doc, "sub_heading"): doc.sub_heading = None
  68. if not meta:
  69. meta = frappe.get_meta(doc.doctype)
  70. jenv = frappe.get_jenv()
  71. format_data, format_data_map = [], {}
  72. # determine template
  73. if print_format:
  74. doc._show_section_headings = print_format.show_section_headings
  75. doc._line_breaks = print_format.line_breaks
  76. doc._align_labels_left = print_format.align_labels_left
  77. if print_format.format_data:
  78. # set format data
  79. format_data = json.loads(print_format.format_data)
  80. for df in format_data:
  81. format_data_map[df.get("fieldname")] = df
  82. if "visible_columns" in df:
  83. for _df in df.get("visible_columns"):
  84. format_data_map[_df.get("fieldname")] = _df
  85. doc.format_data_map = format_data_map
  86. template = "standard"
  87. elif print_format.standard=="Yes" or print_format.custom_format:
  88. template = jenv.from_string(get_print_format(doc.doctype,
  89. print_format))
  90. else:
  91. # fallback
  92. template = "standard"
  93. else:
  94. template = "standard"
  95. if template == "standard":
  96. template = jenv.get_template(standard_format)
  97. letter_head = frappe._dict(get_letter_head(doc, no_letterhead) or {})
  98. convert_markdown(doc, meta)
  99. args = {
  100. "doc": doc,
  101. "meta": frappe.get_meta(doc.doctype),
  102. "layout": make_layout(doc, meta, format_data),
  103. "no_letterhead": no_letterhead,
  104. "trigger_print": cint(trigger_print),
  105. "letter_head": letter_head.content,
  106. "footer": letter_head.footer,
  107. "print_settings": frappe.get_doc("Print Settings")
  108. }
  109. html = template.render(args, filters={"len": len})
  110. if cint(trigger_print):
  111. html += trigger_print_script
  112. return html
  113. def convert_markdown(doc, meta):
  114. '''Convert text field values to markdown if necessary'''
  115. for field in meta.fields:
  116. if field.fieldtype=='Text Editor':
  117. value = doc.get(field.fieldname)
  118. if value and '<!-- markdown -->' in value:
  119. doc.set(field.fieldname, markdown(value))
  120. @frappe.whitelist()
  121. def get_html_and_style(doc, name=None, print_format=None, meta=None,
  122. no_letterhead=None, trigger_print=False):
  123. """Returns `html` and `style` of print format, used in PDF etc"""
  124. if isinstance(doc, basestring) and isinstance(name, basestring):
  125. doc = frappe.get_doc(doc, name)
  126. if isinstance(doc, basestring):
  127. doc = frappe.get_doc(json.loads(doc))
  128. print_format = get_print_format_doc(print_format, meta=meta or frappe.get_meta(doc.doctype))
  129. return {
  130. "html": get_html(doc, name=name, print_format=print_format, meta=meta,
  131. no_letterhead=no_letterhead, trigger_print=trigger_print),
  132. "style": get_print_style(print_format=print_format)
  133. }
  134. def validate_print_permission(doc):
  135. if frappe.form_dict.get("key"):
  136. if frappe.form_dict.key == doc.get_signature():
  137. return
  138. for ptype in ("read", "print"):
  139. if (not frappe.has_permission(doc.doctype, ptype, doc)
  140. and not frappe.has_website_permission(doc)):
  141. raise frappe.PermissionError(_("No {0} permission").format(ptype))
  142. def get_letter_head(doc, no_letterhead):
  143. if no_letterhead:
  144. return {}
  145. if doc.get("letter_head"):
  146. return frappe.db.get_value("Letter Head", doc.letter_head, ["content", "footer"], as_dict=True)
  147. else:
  148. return frappe.db.get_value("Letter Head", {"is_default": 1}, ["content", "footer"], as_dict=True) or {}
  149. def get_print_format(doctype, print_format):
  150. if print_format.disabled:
  151. frappe.throw(_("Print Format {0} is disabled").format(print_format.name),
  152. frappe.DoesNotExistError)
  153. # server, find template
  154. path = os.path.join(get_doc_path(frappe.db.get_value("DocType", doctype, "module"),
  155. "Print Format", print_format.name), frappe.scrub(print_format.name) + ".html")
  156. if os.path.exists(path):
  157. with open(path, "r") as pffile:
  158. return pffile.read()
  159. else:
  160. if print_format.html:
  161. return print_format.html
  162. else:
  163. frappe.throw(_("No template found at path: {0}").format(path),
  164. frappe.TemplateNotFoundError)
  165. def make_layout(doc, meta, format_data=None):
  166. """Builds a hierarchical layout object from the fields list to be rendered
  167. by `standard.html`
  168. :param doc: Document to be rendered.
  169. :param meta: Document meta object (doctype).
  170. :param format_data: Fields sequence and properties defined by Print Format Builder."""
  171. layout, page = [], []
  172. layout.append(page)
  173. if format_data:
  174. # extract print_heading_template from the first field
  175. # and remove the field
  176. if format_data[0].get("fieldname") == "print_heading_template":
  177. doc.print_heading_template = format_data[0].get("options")
  178. format_data = format_data[1:]
  179. def get_new_section(): return {'columns': [], 'has_data': False}
  180. def append_empty_field_dict_to_page_column(page):
  181. """ append empty columns dict to page layout """
  182. if not page[-1]['columns']:
  183. page[-1]['columns'].append({'fields': []})
  184. for df in format_data or meta.fields:
  185. if format_data:
  186. # embellish df with original properties
  187. df = frappe._dict(df)
  188. if df.fieldname:
  189. original = meta.get_field(df.fieldname)
  190. if original:
  191. newdf = original.as_dict()
  192. newdf.update(df)
  193. df = newdf
  194. df.print_hide = 0
  195. if df.fieldtype=="Section Break" or page==[]:
  196. if len(page) > 1:
  197. if page[-1]['has_data']==False:
  198. # truncate last section if empty
  199. del page[-1]
  200. section = get_new_section()
  201. if df.fieldtype=='Section Break' and df.label:
  202. section['label'] = df.label
  203. page.append(section)
  204. elif df.fieldtype=="Column Break":
  205. # if last column break and last column is not empty
  206. page[-1]['columns'].append({'fields': []})
  207. else:
  208. # add a column if not yet added
  209. append_empty_field_dict_to_page_column(page)
  210. if df.fieldtype=="HTML" and df.options:
  211. doc.set(df.fieldname, True) # show this field
  212. if is_visible(df, doc) and has_value(df, doc):
  213. append_empty_field_dict_to_page_column(page)
  214. page[-1]['columns'][-1]['fields'].append(df)
  215. # section has fields
  216. page[-1]['has_data'] = True
  217. # if table, add the row info in the field
  218. # if a page break is found, create a new docfield
  219. if df.fieldtype=="Table":
  220. df.rows = []
  221. df.start = 0
  222. df.end = None
  223. for i, row in enumerate(doc.get(df.fieldname)):
  224. if row.get("page_break"):
  225. # close the earlier row
  226. df.end = i
  227. # new page, with empty section and column
  228. page = [get_new_section()]
  229. layout.append(page)
  230. append_empty_field_dict_to_page_column(page)
  231. # continue the table in a new page
  232. df = copy.copy(df)
  233. df.start = i
  234. df.end = None
  235. page[-1]['columns'][-1]['fields'].append(df)
  236. return layout
  237. def is_visible(df, doc):
  238. """Returns True if docfield is visible in print layout and does not have print_hide set."""
  239. if df.fieldtype in ("Section Break", "Column Break", "Button"):
  240. return False
  241. if hasattr(doc, "hide_in_print_layout"):
  242. if df.fieldname in doc.hide_in_print_layout:
  243. return False
  244. if df.permlevel > 0 and not doc.has_permlevel_access_to(df.fieldname, df):
  245. return False
  246. return not doc.is_print_hide(df.fieldname, df)
  247. def has_value(df, doc):
  248. value = doc.get(df.fieldname)
  249. if value in (None, ""):
  250. return False
  251. elif isinstance(value, basestring) and not strip_html(value).strip():
  252. return False
  253. elif isinstance(value, list) and not len(value):
  254. return False
  255. return True
  256. def get_print_style(style=None, print_format=None, for_legacy=False):
  257. print_settings = frappe.get_doc("Print Settings")
  258. if not style:
  259. style = print_settings.print_style or "Standard"
  260. context = {
  261. "print_settings": print_settings,
  262. "print_style": style,
  263. "font": get_font(print_settings, print_format, for_legacy)
  264. }
  265. css = frappe.get_template("templates/styles/standard.css").render(context)
  266. try:
  267. css += frappe.get_template("templates/styles/" + style.lower() + ".css").render(context)
  268. except TemplateNotFound:
  269. pass
  270. # move @import to top
  271. for at_import in list(set(re.findall("(@import url\([^\)]+\)[;]?)", css))):
  272. css = css.replace(at_import, "")
  273. # prepend css with at_import
  274. css = at_import + css
  275. if print_format and print_format.css:
  276. css += "\n\n" + print_format.css
  277. return css
  278. def get_font(print_settings, print_format=None, for_legacy=False):
  279. default = '"Helvetica Neue", Helvetica, Arial, "Open Sans", sans-serif'
  280. if for_legacy:
  281. return default
  282. font = None
  283. if print_format:
  284. if print_format.font and print_format.font!="Default":
  285. font = '{0}, sans-serif'.format(print_format.font)
  286. if not font:
  287. if print_settings.font and print_settings.font!="Default":
  288. font = '{0}, sans-serif'.format(print_settings.font)
  289. else:
  290. font = default
  291. return font
  292. def get_visible_columns(data, table_meta, df):
  293. """Returns list of visible columns based on print_hide and if all columns have value."""
  294. columns = []
  295. doc = data[0] or frappe.new_doc(df.options)
  296. def add_column(col_df):
  297. return is_visible(col_df, doc) \
  298. and column_has_value(data, col_df.get("fieldname"))
  299. if df.get("visible_columns"):
  300. # columns specified by column builder
  301. for col_df in df.get("visible_columns"):
  302. # load default docfield properties
  303. docfield = table_meta.get_field(col_df.get("fieldname"))
  304. if not docfield:
  305. continue
  306. newdf = docfield.as_dict().copy()
  307. newdf.update(col_df)
  308. if add_column(newdf):
  309. columns.append(newdf)
  310. else:
  311. for col_df in table_meta.fields:
  312. if add_column(col_df):
  313. columns.append(col_df)
  314. return columns
  315. def column_has_value(data, fieldname):
  316. """Check if at least one cell in column has non-zero and non-blank value"""
  317. has_value = False
  318. for row in data:
  319. value = row.get(fieldname)
  320. if value:
  321. if isinstance(value, basestring):
  322. if strip_html(value).strip():
  323. has_value = True
  324. break
  325. else:
  326. has_value = True
  327. break
  328. return has_value
  329. trigger_print_script = """
  330. <script>
  331. window.print();
  332. // close the window after print
  333. // NOTE: doesn't close if print is cancelled in Chrome
  334. setTimeout(function() {
  335. window.close();
  336. }, 1000);
  337. </script>
  338. """