Skip to content
4 changes: 4 additions & 0 deletions frappe/model/base_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
31 changes: 31 additions & 0 deletions frappe/model/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__()
Expand Down Expand Up @@ -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
Expand Down
119 changes: 92 additions & 27 deletions frappe/public/js/frappe/form/controls/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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("");
}
Expand Down Expand Up @@ -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);
}
});

Expand Down
11 changes: 6 additions & 5 deletions frappe/utils/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {})
Expand Down