Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

512 wiersze
15 KiB

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