From cf3074a20b00cc987764d55780b2a9cfcebfa407 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 30 Jan 2015 17:07:45 +0530 Subject: [PATCH] [print-format] completed print format builder --- frappe/__init__.py | 6 +- frappe/client.py | 44 +++--- frappe/config/setup.py | 6 + .../doctype/communication/communication.py | 2 +- frappe/model/base_document.py | 2 +- frappe/model/meta.py | 8 +- .../doctype/print_format/print_format.json | 10 +- .../doctype/print_format/test_print_format.py | 2 +- .../print_format_builder.js | 145 +++++++++++++++--- .../print_format_builder_start.html | 18 +++ frappe/public/css/page.css | 2 +- frappe/public/js/frappe/form/control.js | 37 +++-- frappe/public/js/frappe/form/print.js | 17 ++ .../public/js/frappe/form/print_layout.html | 2 + frappe/public/js/frappe/model/model.js | 1 - frappe/public/less/page.less | 2 +- frappe/templates/pages/print.py | 57 ++++--- 17 files changed, 267 insertions(+), 94 deletions(-) create mode 100644 frappe/print/page/print_format_builder/print_format_builder_start.html diff --git a/frappe/__init__.py b/frappe/__init__.py index 4a0232e624..b92104ca1d 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -856,7 +856,7 @@ def format_value(value, df, doc=None, currency=None): import frappe.utils.formatters return frappe.utils.formatters.format_value(value, df, doc, currency=currency) -def get_print_format(doctype, name, print_format=None, style=None, as_pdf=False): +def get_print(doctype, name, print_format=None, style=None, as_pdf=False): """Get Print Format for given document. :param doctype: DocType of document. @@ -886,12 +886,12 @@ def attach_print(doctype, name, file_name): if int(print_settings.send_print_as_pdf or 0): return { "fname": file_name + ".pdf", - "fcontent": get_print_format(doctype, name, as_pdf=True) + "fcontent": get_print(doctype, name, as_pdf=True) } else: return { "fname": file_name + ".html", - "fcontent": scrub_urls(get_print_format(doctype, name)).encode("utf-8") + "fcontent": scrub_urls(get_print(doctype, name)).encode("utf-8") } logging_setup_complete = False diff --git a/frappe/client.py b/frappe/client.py index 924b3a0b22..50e6f6cfca 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -42,7 +42,7 @@ def set_value(doctype, name, fieldname, value): frappe.throw(_("Cannot edit standard fields")) doc = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True) - if doc and doc.parent: + if doc and doc.parent and doc.parenttype: doc = frappe.get_doc(doc.parenttype, doc.parent) child = doc.getone({"doctype": doctype, "name": name}) child.set(fieldname, value) @@ -59,30 +59,26 @@ def set_value(doctype, name, fieldname, value): return doc.as_dict() @frappe.whitelist() -def insert(doclist): - if isinstance(doclist, basestring): - doclist = json.loads(doclist) +def insert(doc=None): + if isinstance(doc, basestring): + doc = json.loads(doc) - if isinstance(doclist, dict): - doclist = [doclist] - - if doclist[0].get("parent") and doclist[0].get("parenttype"): + if doc.get("parent") and doc.get("parenttype"): # inserting a child record - d = doclist[0] - doc = frappe.get_doc(d["parenttype"], d["parent"]) - doc.append(d) - doc.save() - return [d] + parent = frappe.get_doc(doc.parenttype, doc.parent) + parent.append(doc) + parent.save() + return parent.as_dict() else: - doc = frappe.get_doc(doclist).insert() + doc = frappe.get_doc(doc).insert() return doc.as_dict() @frappe.whitelist() -def save(doclist): - if isinstance(doclist, basestring): - doclist = json.loads(doclist) +def save(doc): + if isinstance(doc, basestring): + doc = json.loads(doc) - doc = frappe.get_doc(doclist).save() + doc = frappe.get_doc(doc).save() return doc.as_dict() @frappe.whitelist() @@ -91,14 +87,14 @@ def rename_doc(doctype, old_name, new_name, merge=False): return new_name @frappe.whitelist() -def submit(doclist): - if isinstance(doclist, basestring): - doclist = json.loads(doclist) +def submit(doc): + if isinstance(doc, basestring): + doc = json.loads(doc) - doclistobj = frappe.get_doc(doclist) - doclistobj.submit() + doc = frappe.get_doc(doc) + doc.submit() - return doclistobj.as_dict() + return doc.as_dict() @frappe.whitelist() def cancel(doctype, name): diff --git a/frappe/config/setup.py b/frappe/config/setup.py index 066750f4a5..1acf10a6ff 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -143,6 +143,12 @@ def get_data(): "label": _("Printing"), "icon": "icon-print", "items": [ + { + "type": "page", + "label": "Print Format Builder", + "name": "print-format-builder", + "description": _("Drag and Drop tool to build and customize Print Formats.") + }, { "type": "doctype", "name": "Print Settings", diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 3224acf6ed..a9cd3793b8 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -187,7 +187,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = def attach_print(mail, parent_doc, print_html, print_format): name = parent_doc.name if parent_doc else "attachment" if (not print_html) and parent_doc and print_format: - print_html = frappe.get_print_format(parent_doc.doctype, parent_doc.name, print_format) + print_html = frappe.get_print(parent_doc.doctype, parent_doc.name, print_format) print_settings = frappe.db.get_singles_dict("Print Settings") send_print_as_pdf = cint(print_settings.send_print_as_pdf) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 1253bb23e2..8c3069c246 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -172,7 +172,7 @@ class BaseDocument(object): if self.get(key): doc[key] = self.get(key) - return doc + return frappe._dict(doc) def as_json(self): return json.dumps(self.as_dict(), indent=1, sort_keys=True) diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 08c73c79e5..f020e94510 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -88,10 +88,10 @@ class Meta(Document): return { "fields": "DocField", "permissions": "DocPerm"}.get(fieldname) def get_field(self, fieldname): - if not fieldname in self._fields: - fields = self.get("fields", {"fieldname":fieldname}) - self._fields[fieldname] = fields[0] if fields else frappe._dict() - return self._fields[fieldname] + if not self._fields: + for f in self.get("fields"): + self._fields[f.fieldname] = f + return self._fields.get(fieldname) def get_label(self, fieldname): return self.get_field(fieldname).label diff --git a/frappe/print/doctype/print_format/print_format.json b/frappe/print/doctype/print_format/print_format.json index 6c8d71b464..998eceb6ac 100644 --- a/frappe/print/doctype/print_format/print_format.json +++ b/frappe/print/doctype/print_format/print_format.json @@ -115,6 +115,14 @@ "permlevel": 0, "precision": "", "read_only": 0 + }, + { + "fieldname": "print_format_builder", + "fieldtype": "Check", + "hidden": 1, + "label": "Print Format Builder", + "permlevel": 0, + "precision": "" } ], "hide_heading": 0, @@ -126,7 +134,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2015-01-28 00:42:25.948759", + "modified": "2015-01-30 02:15:31.686084", "modified_by": "Administrator", "module": "Print", "name": "Print Format", diff --git a/frappe/print/doctype/print_format/test_print_format.py b/frappe/print/doctype/print_format/test_print_format.py index a6b9981b0f..eec0d075a4 100644 --- a/frappe/print/doctype/print_format/test_print_format.py +++ b/frappe/print/doctype/print_format/test_print_format.py @@ -9,7 +9,7 @@ test_records = frappe.get_test_records('Print Format') class TestPrintFormat(unittest.TestCase): def test_print_user(self, style=None): - print_html = frappe.get_print_format("User", "Administrator", style=style) + print_html = frappe.get_print("User", "Administrator", style=style) self.assertTrue("" in print_html) self.assertTrue(re.findall('
[\s]*Administrator[\s]*
', print_html)) return print_html diff --git a/frappe/print/page/print_format_builder/print_format_builder.js b/frappe/print/page/print_format_builder/print_format_builder.js index 70408f384b..23265f6d65 100644 --- a/frappe/print/page/print_format_builder/print_format_builder.js +++ b/frappe/print/page/print_format_builder/print_format_builder.js @@ -3,10 +3,9 @@ frappe.pages['print-format-builder'].on_page_load = function(wrapper) { } frappe.pages['print-format-builder'].on_page_show = function(wrapper) { - // load a new page - var frm = frappe.route_options.print_format - if(!frm || !frappe.print_format_builder.doc || - (frm.doc.name != frappe.print_format_builder.doc.name)) { + if(frappe.route_options) { + frappe.print_format_builder.print_format = frappe.route_options; + frappe.route_options = null; frappe.print_format_builder.refresh(); } } @@ -15,14 +14,13 @@ frappe.PrintFormatBuilder = Class.extend({ init: function(parent) { this.parent = parent; this.make(); + this.refresh(); }, refresh: function() { - this.frm = frappe.route_options.print_format; - - if(!(this.frm && this.frm.doc)) { - this.ask_user_to_start_from_print_format(); + if(!this.print_format) { + this.show_start(); } else { - this.page.set_title(this.frm.doc.name); + this.page.set_title(this.print_format.name); this.setup_print_format(); } }, @@ -31,22 +29,113 @@ frappe.PrintFormatBuilder = Class.extend({ parent: this.parent, title: __("Print Format Builder") }); + + // future-bindings for buttons on sections / fields + // bind only once + this.setup_section_settings(); + this.setup_column_selector(); + this.setup_edit_custom_html(); }, - ask_user_to_start_from_print_format: function() { - this.page.main.html('
' - +__("Please create or edit a new Print Format")+'
'); + show_start: function() { + this.page.main.html(frappe.render_template("print_format_builder_start", {})); this.page.sidebar.html(""); - this.page.clear_primary_action(); + this.page.clear_actions(); + this.page.set_title(__("Print Format Builder")); + this.start_edit_print_format(); + this.start_new_print_format(); + }, + start_edit_print_format: function() { + // print format control + var me = this; + this.print_format_input = frappe.ui.form.make_control({ + parent: this.page.main.find(".print-format-selector"), + df: { + fieldtype: "Link", + options: "Print Format", + filters: { + print_format_builder: 1 + }, + label: __("Select Print Format to Edit"), + only_select: true + }, + render_input: true + }); + + // create a new print format. + this.page.main.find(".btn-edit-print-format").on("click", function() { + var name = me.print_format_input.get_value(); + if(!name) return; + frappe.model.with_doc("Print Format", name, function(doc) { + me.print_format = frappe.get_doc("Print Format", name); + me.refresh(); + }); + }); + }, + start_new_print_format: function() { + var me = this; + this.doctype_input = frappe.ui.form.make_control({ + parent: this.page.main.find(".doctype-selector"), + df: { + fieldtype: "Link", + options: "DocType", + filters: { + "istable": 0, + "issingle": 0 + }, + label: __("Select a DocType to make a new format") + }, + render_input: true + }); + + this.name_input = frappe.ui.form.make_control({ + parent: this.page.main.find(".name-selector"), + df: { + fieldtype: "Data", + label: __("Name of the new Print Format"), + }, + render_input: true + }); + + this.page.main.find(".btn-new-print-format").on("click", function() { + var doctype = me.doctype_input.get_value(), + name = me.name_input.get_value(); + if(!(doctype && name)) { + msgprint(__("Both DocType and Name required")); + return; + } + + frappe.call({ + method: "frappe.client.insert", + args: { + doc: { + doctype: "Print Format", + name: name, + standard: "No", + doc_type: doctype, + print_format_builder: 1 + } + }, + callback: function(r) { + me.print_format = r.message; + me.refresh(); + } + }); + + }); }, setup_print_format: function() { var me = this; - frappe.model.with_doctype(this.frm.doc.doc_type, function(doctype) { - me.meta = frappe.get_meta(me.frm.doc.doc_type); + frappe.model.with_doctype(this.print_format.doc_type, function(doctype) { + me.meta = frappe.get_meta(me.print_format.doc_type); me.setup_sidebar(); me.render_layout(); me.page.set_primary_action(__("Save"), function() { me.save_print_format(); }); + me.page.set_secondary_action(__("Close"), function() { + me.print_format = null; + me.refresh(); + }); }); }, setup_sidebar: function() { @@ -76,13 +165,10 @@ frappe.PrintFormatBuilder = Class.extend({ data: this.layout_data, me: this})) .appendTo(this.page.main); this.setup_sortable(); - this.setup_section_settings(); - this.setup_column_selector(); - this.setup_edit_custom_html(); this.setup_add_section(); }, prepare_data: function() { - this.data = JSON.parse(this.frm.doc.format_data || "[]"); + this.data = JSON.parse(this.print_format.format_data || "[]"); if(!this.data.length) { // new layout this.data = this.meta.fields; @@ -108,7 +194,7 @@ frappe.PrintFormatBuilder = Class.extend({ f.label = "Custom HTML"; f.fieldtype = "Custom HTML" } else { - f = $.extend(frappe.meta.get_docfield(me.frm.doc.doc_type, + f = $.extend(frappe.meta.get_docfield(me.print_format.doc_type, f.fieldname) || {}, f); } } @@ -201,7 +287,7 @@ frappe.PrintFormatBuilder = Class.extend({ if(fieldname==="_custom_html") { var field = me.get_custom_html_field(); } else { - var field = frappe.meta.get_docfield(me.frm.doc.doc_type, fieldname); + var field = frappe.meta.get_docfield(me.print_format.doc_type, fieldname); } $item.replaceWith(frappe.render_template("print_format_builder_field", @@ -456,7 +542,20 @@ frappe.PrintFormatBuilder = Class.extend({ }); }); }); - this.frm.doc.format_data = JSON.stringify(data); - setTimeout(function() { me.frm.save(); }, 100); + + // save format_data + frappe.call({ + method: "frappe.client.set_value", + args: { + doctype: "Print Format", + name: this.print_format.name, + fieldname: "format_data", + value: JSON.stringify(data), + }, + callback: function(r) { + me.print_format = r.message; + msgprint(__("Saved")); + } + }); } }); diff --git a/frappe/print/page/print_format_builder/print_format_builder_start.html b/frappe/print/page/print_format_builder/print_format_builder_start.html new file mode 100644 index 0000000000..dc01bba596 --- /dev/null +++ b/frappe/print/page/print_format_builder/print_format_builder_start.html @@ -0,0 +1,18 @@ +
+

{%= __("Select an existing format to edit or start a new format.") %}

+
+
+ +

+ +

+
+
+
+
+

+ +

+
diff --git a/frappe/public/css/page.css b/frappe/public/css/page.css index dae62357cd..b99fbd430b 100644 --- a/frappe/public/css/page.css +++ b/frappe/public/css/page.css @@ -32,7 +32,7 @@ .page-icon-group a { margin-right: 7px; } -.layout-main { +.page-content { margin-top: 70px; } /* show menu aligned to the right border of the dropdown */ diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 996c76cc2b..0d88941276 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -20,6 +20,10 @@ frappe.ui.form.Control = Class.extend({ frappe.boot.developer_mode===1 && this.$wrapper) { this.$wrapper.attr("title", __(this.df.fieldname)); } + + if(this.render_input) { + this.refresh(); + } }, make: function() { this.make_wrapper(); @@ -944,21 +948,23 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ no_spinner: true, args: args, callback: function(r) { - if(frappe.model.can_create(doctype) - && me.df.fieldtype !== "Dynamic Link") { - // new item + if(!me.df.only_select) { + if(frappe.model.can_create(doctype) + && me.df.fieldtype !== "Dynamic Link") { + // new item + r.results.push({ + value: " " + + __("Create a new {0}", [__(me.df.options)]) + "", + action: me.new_doc + }); + }; + // advanced search r.results.push({ - value: " " - + __("Create a new {0}", [__(me.df.options)]) + "", - action: me.new_doc + value: " " + + __("Advanced Search") + "", + action: me.open_advanced_search }); - }; - // advanced search - r.results.push({ - value: " " - + __("Advanced Search") + "", - action: me.open_advanced_search - }); + } me.$input.cache[doctype][request.term] = r.results; response(r.results); @@ -1032,6 +1038,11 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ } } } + if(this.df.filters) { + set_nulls(this.df.filters); + if(!args.filters) args.filters = {}; + $.extend(args.filters, this.df.filters); + } }, validate: function(value, callback) { // validate the value just entered diff --git a/frappe/public/js/frappe/form/print.js b/frappe/public/js/frappe/form/print.js index 5da67cc35c..95e03cd960 100644 --- a/frappe/public/js/frappe/form/print.js +++ b/frappe/public/js/frappe/form/print.js @@ -8,6 +8,9 @@ frappe.ui.form.PrintPreview = Class.extend({ }, make: function() { this.wrapper = this.frm.page.add_view("print", frappe.render_template("print_layout", {})); + + // only system manager can edit + this.wrapper.find(".btn-print-edit").toggle(frappe.user.has_role("System Manager")); }, bind_events: function() { var me = this; @@ -62,6 +65,20 @@ frappe.ui.form.PrintPreview = Class.extend({ } } }); + + this.wrapper.find(".btn-print-edit").on("click", function() { + var print_format = me.get_print_format(); + if(print_format && print_format.name) { + if(print_format.print_format_builder) { + frappe.route_options = print_format; + frappe.set_route("print-format-builder"); + } else { + frappe.set_route("Form", "Print Format", print_format.name); + } + } else { + msgprint(__("Standard Format Not Editable")); + } + }); }, preview: function() { var me = this; diff --git a/frappe/public/js/frappe/form/print_layout.html b/frappe/public/js/frappe/form/print_layout.html index 704df7fd38..4da96c0f1c 100644 --- a/frappe/public/js/frappe/form/print_layout.html +++ b/frappe/public/js/frappe/form/print_layout.html @@ -13,6 +13,8 @@
{%= __("Print") %} + + {%= __("Edit") %} {%= __("Full Page") %} diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index e35d026f61..12efc7cabf 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -243,7 +243,6 @@ $.extend(frappe.model, { if(doc && doc[fieldname] !== value) { doc[fieldname] = value; frappe.model.trigger(fieldname, value, doc); - setTimeout(function() { console.log(doc[fieldname]) }, 100); return true; } else { // execute link triggers (want to reselect to execute triggers) diff --git a/frappe/public/less/page.less b/frappe/public/less/page.less index 3335f94c24..48881ef20a 100644 --- a/frappe/public/less/page.less +++ b/frappe/public/less/page.less @@ -42,7 +42,7 @@ margin-right: 7px; } -.layout-main { +.page-content { margin-top: 70px; } diff --git a/frappe/templates/pages/print.py b/frappe/templates/pages/print.py index 1dc45b114c..8c21c63bc3 100644 --- a/frappe/templates/pages/print.py +++ b/frappe/templates/pages/print.py @@ -68,16 +68,27 @@ def get_html(doc, name=None, print_format=None, meta=None, meta = frappe.get_meta(doc.doctype) jenv = frappe.get_jenv() + format_data = {} + + # determine template if print_format in ("Standard", standard_format): - template = jenv.get_template("templates/print_formats/standard.html") + template = "standard" else: - template = jenv.from_string(get_print_format(doc.doctype, - print_format)) + print_format = frappe.get_doc("Print Format", print_format) + if print_format.format_data: + format_data = json.loads(print_format.format_data) + template = "standard" + else: + template = jenv.from_string(get_print_format(doc.doctype, + print_format)) + + if template == "standard": + template = jenv.get_template("templates/print_formats/standard.html") args = { "doc": doc, "meta": frappe.get_meta(doc.doctype), - "layout": make_layout(doc, meta), + "layout": make_layout(doc, meta, format_data), "no_letterhead": no_letterhead, "trigger_print": cint(trigger_print), "letter_head": get_letter_head(doc, no_letterhead) @@ -89,7 +100,7 @@ def get_html(doc, name=None, print_format=None, meta=None, @frappe.whitelist() def download_pdf(doctype, name, format=None): - html = frappe.get_print_format(doctype, name, format) + html = frappe.get_print(doctype, name, format) frappe.local.response.filename = "{name}.pdf".format(name=name.replace(" ", "-").replace("/", "-")) frappe.local.response.filecontent = get_pdf(html) frappe.local.response.type = "download" @@ -111,35 +122,41 @@ def get_letter_head(doc, no_letterhead): else: return frappe.db.get_value("Letter Head", {"is_default": 1}, "content") or "" -def get_print_format(doctype, format_name): - if format_name==standard_format: - return format_name - - opts = frappe.db.get_value("Print Format", format_name, "disabled", as_dict=True) - if not opts: - frappe.throw(_("Print Format {0} does not exist").format(format_name), frappe.DoesNotExistError) - elif opts.disabled: - frappe.throw(_("Print Format {0} is disabled").format(format_name), frappe.DoesNotExistError) +def get_print_format(doctype, print_format): + if print_format.disabled: + frappe.throw(_("Print Format {0} is disabled").format(print_format.name), + frappe.DoesNotExistError) # server, find template path = os.path.join(get_doc_path(frappe.db.get_value("DocType", doctype, "module"), - "Print Format", format_name), frappe.scrub(format_name) + ".html") + "Print Format", print_format.name), frappe.scrub(print_format.name) + ".html") if os.path.exists(path): with open(path, "r") as pffile: return pffile.read() else: - html = frappe.db.get_value("Print Format", format_name, "html") - if html: - return html + if print_format.html: + return print_format.html else: frappe.throw(_("No template found at path: {0}").format(path), frappe.TemplateNotFoundError) -def make_layout(doc, meta): +def make_layout(doc, meta, format_data=None): layout, page = [], [] layout.append(page) - for df in meta.fields: + for df in format_data or meta.fields: + if format_data: + # embellish df with original properties + df = frappe._dict(df) + if df.fieldname: + original = meta.get_field(df.fieldname) + if original: + newdf = original.as_dict() + newdf.update(df) + df = newdf + + df.print_hide = 0 + if df.fieldtype=="Section Break" or page==[]: page.append([])