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..5fad23c7aa 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 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 + + 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..0bf1423c99 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -24,6 +24,9 @@ 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); + if(me.$input.val() && me.get_options()) { let doctype = me.get_options(); let name = me.$input.val(); @@ -43,6 +46,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 +64,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(); + } + } + 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.$input.is(":focus")) { + 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 +257,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.value && 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 +311,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 +468,64 @@ 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){ + 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++) { + 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]; + 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); + }, 100); } }); 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 {})