From 543f89da7deb2d84ff7be4401bc20244aca16a8d Mon Sep 17 00:00:00 2001 From: Martin Zoller Date: Fri, 23 Jun 2023 17:44:37 +0200 Subject: [PATCH 1/7] New feature: Always fetch and show titles of link fields --- frappe/model/base_document.py | 4 + frappe/model/document.py | 31 +++++ frappe/public/js/frappe/form/controls/link.js | 113 +++++++++++++----- 3 files changed, 121 insertions(+), 27 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 1bde485ac4..77909f4b29 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -271,6 +271,10 @@ def is_new(self): def as_dict(self, no_nulls=False, no_default_fields=False, convert_dates_to_str=False): doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str) doc["doctype"] = self.doctype + if hasattr(self,'link_titles'): + doc["link_titles"] = self.link_titles + if hasattr(self,'link_values'): + doc["link_values"] = self.link_values for df in self.meta.get_table_fields(): children = self.get(df.fieldname) or [] doc[df.fieldname] = [d.as_dict(no_nulls=no_nulls) for d in children] diff --git a/frappe/model/document.py b/frappe/model/document.py index f93c366ffb..4045eb6523 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -163,6 +163,8 @@ def load_from_db(self): else: self.set(df.fieldname, []) + self.fetch_link_titles() + # sometimes __setup__ can depend on child values, hence calling again at the end if hasattr(self, "__setup__"): self.__setup__() @@ -702,6 +704,35 @@ def set_name_in_children(self): if not d.name: set_new_name(d) + def fetch_link_titles(self): + """Stores the titles of all linked documents in self.link_titles[link_field_name], + and the associated values (doc names) in self.link_values[link_field_name]. The values + are provided to check whether the link title still matches the currently selected linked doc.""" + + if getattr(self, "_metaclass", False): + return + + link_titles = {} + link_values = {} + + for df in self.meta.get_link_fields(): + docname = self.get(df.fieldname) + + if docname: + if df.fieldtype != "Link": + frappe.throw(_("Link field {0} does not have doctype 'Link'").format(df.fieldname)) + doctype = df.options + if not doctype: + frappe.throw(_("Options not set for link field {0}").format(df.fieldname)) + + meta = frappe.get_meta(doctype) + if meta.title_field: + link_titles[df.fieldname] = frappe.db.get_value(doctype, docname, meta.title_field, cache=True) + link_values[df.fieldname] = docname + + self.set("link_titles", link_titles) + self.set("link_values", link_values) + def validate_update_after_submit(self): if self.flags.ignore_validate_update_after_submit: return diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index f206cd12d8..7238dac5cf 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -24,6 +24,10 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ this.set_input_attributes(); this.$input.on("focus", function() { setTimeout(function() { + // When the field is clicked, put just the value into it (remove the field title) + me.set_formatted_input(me.value); + me.awesomplete.open(); + if(me.$input.val() && me.get_options()) { let doctype = me.get_options(); let name = me.$input.val(); @@ -43,6 +47,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ me.$link.toggle(false); }, 500); }); + this.$input.attr('data-target', this.df.options); this.input = this.$input.get(0); this.has_input = true; @@ -60,6 +65,37 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ return frappe.get_route && frappe.get_route()[0] === 'List' ? frappe.get_route()[1] : null; } }, + get_input_value: function() { + if(this.frm) { + var formfield = this.frm.fields_dict[this.df.fieldname]; + if(typeof formfield !== 'undefined') { + return formfield.get_model_value(); + } else { + return this.$input ? this.$input.val() : undefined; + } + } + }, + set_input: function(value) { + this.value = value; + + // Clear the link field title if the value has changed + // (in that case a new title will be fetched and this function will be called again by set_fetch_values) + if(this.frm && this.frm.doc.link_values && this.frm.doc.link_values[this.df.fieldname] != this.value) { + this.frm.doc.link_titles[this.df.fieldname] = null; + } + + // Set the field title if still available + if(this.frm && this.frm.doc.link_titles && value != null + && this.frm.doc.link_titles[this.df.fieldname] != null + && this.frm.doc.link_titles[this.df.fieldname] != value) { + this.formatted_value = value + ': ' + this.frm.doc.link_titles[this.df.fieldname]; + } + else { + this.formatted_value = value; + } + this.set_formatted_input(this.formatted_value); + this.set_mandatory && this.set_mandatory(value); + }, setup_buttons: function() { if(this.only_input && !this.with_link_btn) { this.$input_area.find(".link-btn").remove(); @@ -222,10 +258,15 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ me.selected = false; return; } - var value = me.get_input_value(); - if(value!==me.last_value) { + var value = me.$input.val(); + + // Lost focus: Revalidate the field value if it was changed manually + if(value !== me.formatted_value) { me.parse_validate_and_set_in_model(value); } + + // Add back the field title if available + me.set_input(me.value); }); this.$input.on("awesomplete-open", function() { @@ -271,6 +312,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ this.$input.on("awesomplete-selectcomplete", function(e) { var o = e.originalEvent; + if(o.text.value.indexOf("__link_option") !== -1) { me.$input.val(""); } @@ -427,40 +469,57 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ if(value) { return new Promise((resolve) => { - var fetch = ''; + let fetch = []; + let ref_dt = this.get_options(); if(this.frm && this.frm.fetch_dict[df.fieldname]) { - fetch = this.frm.fetch_dict[df.fieldname].columns.join(', '); + fetch = this.frm.fetch_dict[df.fieldname].columns; } - return frappe.call({ - method:'frappe.desk.form.utils.validate_link', - type: "GET", - args: { - 'value': value, - 'options': doctype, - 'fetch': fetch - }, - no_spinner: true, - callback: function(r) { - if(r.message=='Ok') { - if(r.fetch_values && docname) { - me.set_fetch_values(df, docname, r.fetch_values); - } - resolve(r.valid_value); - } else { - resolve(""); - } - } + frappe.model.with_doctype(ref_dt, function () { + let title_field = frappe.get_meta(ref_dt).title_field || "name"; + fetch.push(title_field); + fetch = fetch.join(', '); + + return frappe.call({ + method:'frappe.desk.form.utils.validate_link', + type: "GET", + args: { + 'value': value, + 'options': doctype, + 'fetch': fetch + }, + no_spinner: true, + callback: function(r) { + if(r.message=='Ok') { + if(r.fetch_values && docname) { + me.set_fetch_values(df, docname, value, r.fetch_values); + } + resolve(r.valid_value); + } else { + resolve(""); + } + } + }); }); }); } }, - set_fetch_values: function(df, docname, fetch_values) { - var fl = this.frm.fetch_dict[df.fieldname].fields; - for(var i=0; i < fl.length; i++) { - frappe.model.set_value(df.parent, docname, fl[i], fetch_values[i], df.fieldtype); + set_fetch_values: function(df, docname, value, fetch_values) { + var me = this; + if(me.frm.fetch_dict[df.fieldname]) { + var fl = me.frm.fetch_dict[df.fieldname].fields; + for(var i=0; i < fl.length; i++) { + frappe.model.set_value(df.parent, docname, fl[i], fetch_values[i], df.fieldtype); + } } + // Even if fetch_dict is empty, the link title has been fetched, now show it in the field + setTimeout(function() { + var new_title = fetch_values[fetch_values.length - 1]; + me.frm.doc.link_titles[df.fieldname] = new_title; + me.frm.doc.link_values[df.fieldname] = value; + me.set_input(me.value); + }, 100); } }); From a44d41cb2ed45de041d3b984a113c49afb0490a6 Mon Sep 17 00:00:00 2001 From: Martin Zoller Date: Fri, 30 Jun 2023 09:55:12 +0200 Subject: [PATCH 2/7] JS bugfix on link title feature: Initialize link title arrays when needed (new documents) --- frappe/public/js/frappe/form/controls/link.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 7238dac5cf..7bdab21222 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -516,6 +516,10 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ // Even if fetch_dict is empty, the link title has been fetched, now show it in the field setTimeout(function() { var new_title = fetch_values[fetch_values.length - 1]; + if(typeof me.frm.doc.link_titles === 'undefined') { + me.frm.doc.link_titles = {}; + me.frm.doc.link_values = {}; + } me.frm.doc.link_titles[df.fieldname] = new_title; me.frm.doc.link_values[df.fieldname] = value; me.set_input(me.value); From f0c51d2e4776e07008ff14151dcc3dafb66d41c6 Mon Sep 17 00:00:00 2001 From: Martin Zoller Date: Fri, 30 Jun 2023 12:04:34 +0200 Subject: [PATCH 3/7] JS bugfix on link title feature: Make sure get_input_value() always returns a value --- frappe/public/js/frappe/form/controls/link.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 7bdab21222..5c4ead53b9 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -70,10 +70,9 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ var formfield = this.frm.fields_dict[this.df.fieldname]; if(typeof formfield !== 'undefined') { return formfield.get_model_value(); - } else { - return this.$input ? this.$input.val() : undefined; } } + return this.$input ? this.$input.val() : undefined; }, set_input: function(value) { this.value = value; From 98ea03d558f5c98d2c3f68f9355834612a4d1f3f Mon Sep 17 00:00:00 2001 From: Martin Zoller Date: Mon, 10 Jul 2023 11:30:46 +0200 Subject: [PATCH 4/7] JS bugfix on link title feature: Now works correctly with auto-reloads, eg. when selecting customer in a sales document --- frappe/public/js/frappe/form/controls/link.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 5c4ead53b9..00a691015b 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -26,7 +26,6 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ setTimeout(function() { // When the field is clicked, put just the value into it (remove the field title) me.set_formatted_input(me.value); - me.awesomplete.open(); if(me.$input.val() && me.get_options()) { let doctype = me.get_options(); @@ -86,8 +85,9 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ // Set the field title if still available if(this.frm && this.frm.doc.link_titles && value != null && this.frm.doc.link_titles[this.df.fieldname] != null - && this.frm.doc.link_titles[this.df.fieldname] != value) { - this.formatted_value = value + ': ' + this.frm.doc.link_titles[this.df.fieldname]; + && this.frm.doc.link_titles[this.df.fieldname] != value + && !this.$input.is(":focus")) { + this.formatted_value = value + ': ' + this.frm.doc.link_titles[this.df.fieldname]; } else { this.formatted_value = value; @@ -260,7 +260,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ var value = me.$input.val(); // Lost focus: Revalidate the field value if it was changed manually - if(value !== me.formatted_value) { + if(value != me.value && value != me.formatted_value) { me.parse_validate_and_set_in_model(value); } From 7b7d363eb1c8b140fd8792cd8f719aa5db479f56 Mon Sep 17 00:00:00 2001 From: Martin Zoller Date: Thu, 3 Aug 2023 13:19:19 +0200 Subject: [PATCH 5/7] Python bugfix on link title feature: Only access meta.title_field if it exists --- frappe/model/document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/document.py b/frappe/model/document.py index 4045eb6523..5fad23c7aa 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -726,7 +726,7 @@ def fetch_link_titles(self): frappe.throw(_("Options not set for link field {0}").format(df.fieldname)) meta = frappe.get_meta(doctype) - if meta.title_field: + if hasattr(meta, "title_field") and meta.title_field: link_titles[df.fieldname] = frappe.db.get_value(doctype, docname, meta.title_field, cache=True) link_values[df.fieldname] = docname From eeb5488de6cd52ad58dfdbe70d4690c7dea7d214 Mon Sep 17 00:00:00 2001 From: Martin Zoller Date: Fri, 25 Aug 2023 15:30:51 +0200 Subject: [PATCH 6/7] JS fix for link title feature to avoid issues with quick entry form --- frappe/public/js/frappe/form/controls/link.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 00a691015b..0bf1423c99 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -506,6 +506,9 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ }, set_fetch_values: function(df, docname, value, fetch_values) { var me = this; + if(!me.frm){ + return; + } if(me.frm.fetch_dict[df.fieldname]) { var fl = me.frm.fetch_dict[df.fieldname].fields; for(var i=0; i < fl.length; i++) { From 53e5330874fb09be9d704dfc055a7c95b8ea9590 Mon Sep 17 00:00:00 2001 From: Martin Zoller Date: Mon, 11 Dec 2023 15:29:01 +0100 Subject: [PATCH 7/7] get_pdf: Retrieve smart shrinking option even when a default print format is used --- frappe/utils/pdf.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 92a72d7223..ba2bc44c45 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -8,6 +8,7 @@ import six, re, io from bs4 import BeautifulSoup from PyPDF2 import PdfFileReader, PdfFileWriter +from frappe.www.printview import get_print_format_doc def get_pdf(html, options=None, output=None, print_format=None): html = scrub_urls(html) @@ -17,16 +18,16 @@ def get_pdf(html, options=None, output=None, print_format=None): "disable-javascript": "", "disable-local-file-access": "", }) - + # add options from print format - if print_format and frappe.db.exists("Print Format", print_format): - pf = frappe.get_doc("Print Format", print_format) - if cint(pf.disable_smart_shrinking) == 1: + print_format_doc = get_print_format_doc(print_format, frappe.get_meta(frappe.form_dict.doctype)) + if print_format_doc: + if cint(print_format_doc.disable_smart_shrinking) == 1: options.update({ "disable-smart-shrinking": "" }) + filedata = '' - try: # Set filename property to false, so no file is actually created filedata = pdfkit.from_string(html, False, options=options or {})