Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

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