@@ -1,5 +1,6 @@ | |||||
language: python | language: python | ||||
dist: trusty | dist: trusty | ||||
group: deprecated-2017Q2 | |||||
python: | python: | ||||
- "2.7" | - "2.7" | ||||
@@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json | |||||
from .exceptions import * | from .exceptions import * | ||||
from .utils.jinja import get_jenv, get_template, render_template | from .utils.jinja import get_jenv, get_template, render_template | ||||
__version__ = '8.1.1' | |||||
__version__ = '8.1.2' | |||||
__title__ = "Frappe Framework" | __title__ = "Frappe Framework" | ||||
local = Local() | local = Local() | ||||
@@ -379,7 +379,8 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message | |||||
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, | unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, | ||||
attachments=None, content=None, doctype=None, name=None, reply_to=None, | attachments=None, content=None, doctype=None, name=None, reply_to=None, | ||||
cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, | cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, | ||||
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False): | |||||
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False, | |||||
inline_images=None): | |||||
"""Send email using user's default **Email Account** or global default **Email Account**. | """Send email using user's default **Email Account** or global default **Email Account**. | ||||
@@ -401,6 +402,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message | |||||
:param send_after: Send after the given datetime. | :param send_after: Send after the given datetime. | ||||
:param expose_recipients: Display all recipients in the footer message - "This email was sent to" | :param expose_recipients: Display all recipients in the footer message - "This email was sent to" | ||||
:param communication: Communication link to be set in Email Queue record | :param communication: Communication link to be set in Email Queue record | ||||
:param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id | |||||
""" | """ | ||||
message = content or message | message = content or message | ||||
@@ -418,7 +420,8 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message | |||||
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, | unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, | ||||
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to, | attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to, | ||||
send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, | send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, | ||||
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification) | |||||
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification, | |||||
inline_images=inline_images) | |||||
whitelisted = [] | whitelisted = [] | ||||
guest_methods = [] | guest_methods = [] | ||||
@@ -19,7 +19,7 @@ frappe.ui.form.on("Contact", { | |||||
if(!frm.doc.user && !frm.is_new() && frm.perm[0].write) { | if(!frm.doc.user && !frm.is_new() && frm.perm[0].write) { | ||||
frm.add_custom_button(__("Invite as User"), function() { | frm.add_custom_button(__("Invite as User"), function() { | ||||
frappe.call({ | frappe.call({ | ||||
method: "frappe.email.doctype.contact.contact.invite_user", | |||||
method: "frappe.contacts.doctype.contact.contact.invite_user", | |||||
args: { | args: { | ||||
contact: frm.doc.name | contact: frm.doc.name | ||||
}, | }, | ||||
@@ -17,20 +17,20 @@ frappe.listview_settings['File'] = { | |||||
}, | }, | ||||
prepare_data: function(data) { | prepare_data: function(data) { | ||||
// set image icons | // set image icons | ||||
var icon = "" | |||||
var icon = ""; | |||||
if(data.is_folder) { | if(data.is_folder) { | ||||
icon += '<i class="fa fa-folder-close-alt fa-fw"></i> '; | icon += '<i class="fa fa-folder-close-alt fa-fw"></i> '; | ||||
} else if(frappe.utils.is_image_file(data.file_name)) { | } else if(frappe.utils.is_image_file(data.file_name)) { | ||||
icon += '<i class="fa fa-picture fa-fw"></i> '; | icon += '<i class="fa fa-picture fa-fw"></i> '; | ||||
} else { | } else { | ||||
icon += '<i class="fa fa-file-alt fa-fw"></i> ' | |||||
icon += '<i class="fa fa-file-alt fa-fw"></i> '; | |||||
} | } | ||||
data._title = icon + (data.file_name ? data.file_name : data.file_url) | |||||
data._title = icon + (data.file_name ? data.file_name : data.file_url); | |||||
if (data.is_private) { | if (data.is_private) { | ||||
data._title += ' <i class="fa fa-lock fa-fw text-warning"></i>' | |||||
data._title += ' <i class="fa fa-lock fa-fw text-warning"></i>'; | |||||
} | } | ||||
}, | }, | ||||
onload: function(doclist) { | onload: function(doclist) { | ||||
@@ -43,7 +43,7 @@ frappe.listview_settings['File'] = { | |||||
doclist.$page.on("click", ".list-row-checkbox", function(event) { | doclist.$page.on("click", ".list-row-checkbox", function(event) { | ||||
doclist.list_renderer.settings.add_menu_item_copy(doclist); | doclist.list_renderer.settings.add_menu_item_copy(doclist); | ||||
}) | |||||
}); | |||||
}, | }, | ||||
list_view_doc:function(doclist){ | list_view_doc:function(doclist){ | ||||
$(doclist.wrapper).on("click", 'button[list_view_doc="'+doclist.doctype+'"]', function() { | $(doclist.wrapper).on("click", 'button[list_view_doc="'+doclist.doctype+'"]', function() { | ||||
@@ -73,7 +73,7 @@ frappe.listview_settings['File'] = { | |||||
method: "frappe.core.doctype.file.file.create_new_folder", | method: "frappe.core.doctype.file.file.create_new_folder", | ||||
args: data, | args: data, | ||||
callback: function(r) { } | callback: function(r) { } | ||||
}) | |||||
}); | |||||
}, __('Enter folder name'), __("Create")); | }, __('Enter folder name'), __("Create")); | ||||
}); | }); | ||||
@@ -134,7 +134,7 @@ frappe.listview_settings['File'] = { | |||||
else{ | else{ | ||||
frappe.throw(__("Please select file to copy")); | frappe.throw(__("Please select file to copy")); | ||||
} | } | ||||
}) | |||||
}); | |||||
doclist.copy = true; | doclist.copy = true; | ||||
} | } | ||||
}, | }, | ||||
@@ -235,5 +235,5 @@ frappe.listview_settings['File'] = { | |||||
.appendTo(doclist.breadcrumb); | .appendTo(doclist.breadcrumb); | ||||
} | } | ||||
}); | }); | ||||
}; | |||||
} | |||||
}; | }; |
@@ -120,6 +120,8 @@ def export_query(): | |||||
data = frappe._dict(frappe.local.form_dict) | data = frappe._dict(frappe.local.form_dict) | ||||
del data["cmd"] | del data["cmd"] | ||||
if "csrf_token" in data: | |||||
del data["csrf_token"] | |||||
if isinstance(data.get("filters"), basestring): | if isinstance(data.get("filters"), basestring): | ||||
filters = json.loads(data["filters"]) | filters = json.loads(data["filters"]) | ||||
@@ -27,6 +27,8 @@ def get_form_params(): | |||||
data = frappe._dict(frappe.local.form_dict) | data = frappe._dict(frappe.local.form_dict) | ||||
del data["cmd"] | del data["cmd"] | ||||
if "csrf_token" in data: | |||||
del data["csrf_token"] | |||||
if isinstance(data.get("filters"), basestring): | if isinstance(data.get("filters"), basestring): | ||||
data["filters"] = json.loads(data["filters"]) | data["filters"] = json.loads(data["filters"]) | ||||
@@ -2,19 +2,20 @@ | |||||
# MIT License. See license.txt | # MIT License. See license.txt | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | |||||
import frappe, re | |||||
from frappe.utils.pdf import get_pdf | from frappe.utils.pdf import get_pdf | ||||
from frappe.email.smtp import get_outgoing_email_account | from frappe.email.smtp import get_outgoing_email_account | ||||
from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint, | from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint, | ||||
split_emails, to_markdown, markdown, encode, random_string) | |||||
split_emails, to_markdown, markdown, encode, random_string, parse_addr) | |||||
import email.utils | import email.utils | ||||
from frappe.utils import parse_addr | |||||
from six import iteritems | from six import iteritems | ||||
from email.mime.multipart import MIMEMultipart | |||||
def get_email(recipients, sender='', msg='', subject='[No Subject]', | def get_email(recipients, sender='', msg='', subject='[No Subject]', | ||||
text_content = None, footer=None, print_html=None, formatted=None, attachments=None, | text_content = None, footer=None, print_html=None, formatted=None, attachments=None, | ||||
content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None): | |||||
content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None, | |||||
inline_images=[]): | |||||
"""send an html email as multipart with attachments and all""" | """send an html email as multipart with attachments and all""" | ||||
content = content or msg | content = content or msg | ||||
emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account, expose_recipients=expose_recipients) | emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account, expose_recipients=expose_recipients) | ||||
@@ -22,7 +23,8 @@ def get_email(recipients, sender='', msg='', subject='[No Subject]', | |||||
if not content.strip().startswith("<"): | if not content.strip().startswith("<"): | ||||
content = markdown(content) | content = markdown(content) | ||||
emailobj.set_html(content, text_content, footer=footer, print_html=print_html, formatted=formatted) | |||||
emailobj.set_html(content, text_content, footer=footer, | |||||
print_html=print_html, formatted=formatted, inline_images=inline_images) | |||||
if isinstance(attachments, dict): | if isinstance(attachments, dict): | ||||
attachments = [attachments] | attachments = [attachments] | ||||
@@ -39,7 +41,6 @@ class EMail: | |||||
Also sets all messages as multipart/alternative for cleaner reading in text-only clients | Also sets all messages as multipart/alternative for cleaner reading in text-only clients | ||||
""" | """ | ||||
def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None, expose_recipients=None): | def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None, expose_recipients=None): | ||||
from email.mime.multipart import MIMEMultipart | |||||
from email import Charset | from email import Charset | ||||
Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') | Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') | ||||
@@ -64,7 +65,8 @@ class EMail: | |||||
self.email_account = email_account or get_outgoing_email_account() | self.email_account = email_account or get_outgoing_email_account() | ||||
def set_html(self, message, text_content = None, footer=None, print_html=None, formatted=None): | |||||
def set_html(self, message, text_content = None, footer=None, print_html=None, | |||||
formatted=None, inline_images=None): | |||||
"""Attach message in the html portion of multipart/alternative""" | """Attach message in the html portion of multipart/alternative""" | ||||
if not formatted: | if not formatted: | ||||
formatted = get_formatted_html(self.subject, message, footer, print_html, email_account=self.email_account) | formatted = get_formatted_html(self.subject, message, footer, print_html, email_account=self.email_account) | ||||
@@ -77,7 +79,7 @@ class EMail: | |||||
else: | else: | ||||
self.set_html_as_text(expand_relative_urls(formatted)) | self.set_html_as_text(expand_relative_urls(formatted)) | ||||
self.set_part_html(formatted) | |||||
self.set_part_html(formatted, inline_images) | |||||
self.html_set = True | self.html_set = True | ||||
def set_text(self, message): | def set_text(self, message): | ||||
@@ -88,10 +90,28 @@ class EMail: | |||||
part = MIMEText(message, 'plain', 'utf-8') | part = MIMEText(message, 'plain', 'utf-8') | ||||
self.msg_multipart.attach(part) | self.msg_multipart.attach(part) | ||||
def set_part_html(self, message): | |||||
def set_part_html(self, message, inline_images): | |||||
from email.mime.text import MIMEText | from email.mime.text import MIMEText | ||||
part = MIMEText(message, 'html', 'utf-8') | |||||
self.msg_multipart.attach(part) | |||||
if inline_images: | |||||
related = MIMEMultipart('related') | |||||
for image in inline_images: | |||||
# images in dict like {filename:'', filecontent:'raw'} | |||||
content_id = random_string(10) | |||||
# replace filename in message with CID | |||||
message = re.sub('''src=['"]{0}['"]'''.format(image.get('filename')), | |||||
'src="cid:{0}"'.format(content_id), message) | |||||
self.add_attachment(image.get('filename'), image.get('filecontent'), | |||||
None, content_id=content_id, parent=related) | |||||
html_part = MIMEText(message, 'html', 'utf-8') | |||||
related.attach(html_part) | |||||
self.msg_multipart.attach(related) | |||||
else: | |||||
self.msg_multipart.attach(MIMEText(message, 'html', 'utf-8')) | |||||
def set_html_as_text(self, html): | def set_html_as_text(self, html): | ||||
"""return html2text""" | """return html2text""" | ||||
@@ -118,7 +138,8 @@ class EMail: | |||||
self.add_attachment(res[0], res[1]) | self.add_attachment(res[0], res[1]) | ||||
def add_attachment(self, fname, fcontent, content_type=None): | |||||
def add_attachment(self, fname, fcontent, content_type=None, | |||||
parent=None, content_id=None): | |||||
"""add attachment""" | """add attachment""" | ||||
from email.mime.audio import MIMEAudio | from email.mime.audio import MIMEAudio | ||||
from email.mime.base import MIMEBase | from email.mime.base import MIMEBase | ||||
@@ -155,8 +176,13 @@ class EMail: | |||||
if fname: | if fname: | ||||
part.add_header(b'Content-Disposition', | part.add_header(b'Content-Disposition', | ||||
("attachment; filename=\"%s\"" % fname).encode('utf-8')) | ("attachment; filename=\"%s\"" % fname).encode('utf-8')) | ||||
if content_id: | |||||
part.add_header(b'Content-ID', '<{0}>'.format(content_id)) | |||||
self.msg_root.attach(part) | |||||
if not parent: | |||||
parent = self.msg_root | |||||
parent.attach(part) | |||||
def add_pdf_attachment(self, name, html, options=None): | def add_pdf_attachment(self, name, html, options=None): | ||||
self.add_attachment(name, get_pdf(html, options), 'application/octet-stream') | self.add_attachment(name, get_pdf(html, options), 'application/octet-stream') | ||||
@@ -21,7 +21,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||||
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, | reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, | ||||
attachments=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None, | attachments=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None, | ||||
expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None, | expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None, | ||||
queue_separately=False, is_notification=False, add_unsubscribe_link=1): | |||||
queue_separately=False, is_notification=False, add_unsubscribe_link=1, inline_images=None): | |||||
"""Add email to sending queue (Email Queue) | """Add email to sending queue (Email Queue) | ||||
:param recipients: List of recipients. | :param recipients: List of recipients. | ||||
@@ -42,6 +42,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||||
:param queue_separately: Queue each email separately | :param queue_separately: Queue each email separately | ||||
:param is_notification: Marks email as notification so will not trigger notifications from system | :param is_notification: Marks email as notification so will not trigger notifications from system | ||||
:param add_unsubscribe_link: Send unsubscribe link in the footer of the Email, default 1. | :param add_unsubscribe_link: Send unsubscribe link in the footer of the Email, default 1. | ||||
:param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id | |||||
""" | """ | ||||
if not unsubscribe_method: | if not unsubscribe_method: | ||||
unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe" | unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe" | ||||
@@ -112,6 +113,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||||
read_receipt=read_receipt, | read_receipt=read_receipt, | ||||
queue_separately=queue_separately, | queue_separately=queue_separately, | ||||
is_notification = is_notification, | is_notification = is_notification, | ||||
inline_images = inline_images, | |||||
now=now) | now=now) | ||||
@@ -152,7 +154,8 @@ def get_email_queue(recipients, sender, subject, **kwargs): | |||||
reply_to=kwargs.get('reply_to'), | reply_to=kwargs.get('reply_to'), | ||||
cc=kwargs.get('cc'), | cc=kwargs.get('cc'), | ||||
email_account=kwargs.get('email_account'), | email_account=kwargs.get('email_account'), | ||||
expose_recipients=kwargs.get('expose_recipients')) | |||||
expose_recipients=kwargs.get('expose_recipients'), | |||||
inline_images=kwargs.get('inline_images')) | |||||
mail.set_message_id(kwargs.get('message_id'),kwargs.get('is_notification')) | mail.set_message_id(kwargs.get('message_id'),kwargs.get('is_notification')) | ||||
if kwargs.get('read_receipt'): | if kwargs.get('read_receipt'): | ||||
@@ -431,7 +434,7 @@ def prepare_message(email, recipient, recipients_list): | |||||
message = email.message | message = email.message | ||||
if not message: | if not message: | ||||
return "" | return "" | ||||
if email.add_unsubscribe_link and email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url | if email.add_unsubscribe_link and email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url | ||||
unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient, | unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient, | ||||
email.unsubscribe_method, email.unsubscribe_params) | email.unsubscribe_method, email.unsubscribe_params) | ||||
@@ -461,19 +464,19 @@ def clear_outbox(): | |||||
Called daily via scheduler. | Called daily via scheduler. | ||||
Note: Used separate query to avoid deadlock | Note: Used separate query to avoid deadlock | ||||
""" | """ | ||||
email_queues = frappe.db.sql_list("""select name from `tabEmail Queue` | |||||
email_queues = frappe.db.sql_list("""select name from `tabEmail Queue` | |||||
where priority=0 and datediff(now(), modified) > 31""") | where priority=0 and datediff(now(), modified) > 31""") | ||||
if email_queues: | if email_queues: | ||||
frappe.db.sql("""delete from `tabEmail Queue` where name in (%s)""" | |||||
frappe.db.sql("""delete from `tabEmail Queue` where name in (%s)""" | |||||
% ','.join(['%s']*len(email_queues)), tuple(email_queues)) | % ','.join(['%s']*len(email_queues)), tuple(email_queues)) | ||||
frappe.db.sql("""delete from `tabEmail Queue Recipient` where parent in (%s)""" | |||||
frappe.db.sql("""delete from `tabEmail Queue Recipient` where parent in (%s)""" | |||||
% ','.join(['%s']*len(email_queues)), tuple(email_queues)) | % ','.join(['%s']*len(email_queues)), tuple(email_queues)) | ||||
for dt in ("Email Queue", "Email Queue Recipient"): | for dt in ("Email Queue", "Email Queue Recipient"): | ||||
frappe.db.sql(""" | frappe.db.sql(""" | ||||
update `tab{0}` | |||||
update `tab{0}` | |||||
set status='Expired' | set status='Expired' | ||||
where datediff(curdate(), modified) > 7 and status='Not Sent'""".format(dt)) | where datediff(curdate(), modified) > 7 and status='Not Sent'""".format(dt)) |
@@ -50,6 +50,14 @@ | |||||
.avatar-large .standard-image { | .avatar-large .standard-image { | ||||
font-size: 36px; | font-size: 36px; | ||||
} | } | ||||
.avatar-xl { | |||||
margin-right: 10px; | |||||
width: 108px; | |||||
height: 108px; | |||||
} | |||||
.avatar-xl .standard-image { | |||||
font-size: 72px; | |||||
} | |||||
.avatar-xs { | .avatar-xs { | ||||
margin-right: 3px; | margin-right: 3px; | ||||
margin-top: -2px; | margin-top: -2px; | ||||
@@ -100,7 +100,7 @@ frappe.ui.form.Control = Class.extend({ | |||||
set_value: function(value) { | set_value: function(value) { | ||||
this.parse_validate_and_set_in_model(value); | this.parse_validate_and_set_in_model(value); | ||||
}, | }, | ||||
parse_validate_and_set_in_model: function(value) { | |||||
parse_validate_and_set_in_model: function(value, e) { | |||||
var me = this; | var me = this; | ||||
if(this.inside_change_event) return; | if(this.inside_change_event) return; | ||||
this.inside_change_event = true; | this.inside_change_event = true; | ||||
@@ -110,6 +110,11 @@ frappe.ui.form.Control = Class.extend({ | |||||
me.set_model_value(value); | me.set_model_value(value); | ||||
me.inside_change_event = false; | me.inside_change_event = false; | ||||
me.set_mandatory && me.set_mandatory(value); | me.set_mandatory && me.set_mandatory(value); | ||||
if(me.df.change || me.df.onchange) { | |||||
// onchange event specified in df | |||||
(me.df.change || me.df.onchange).apply(me, [e]); | |||||
} | |||||
} | } | ||||
this.validate ? this.validate(value, set) : set(value); | this.validate ? this.validate(value, set) : set(value); | ||||
@@ -333,39 +338,8 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||||
bind_change_event: function() { | bind_change_event: function() { | ||||
var me = this; | var me = this; | ||||
this.$input && this.$input.on("change", this.change || function(e) { | this.$input && this.$input.on("change", this.change || function(e) { | ||||
if(me.df.change || me.df.onchange) { | |||||
// onchange event specified in df | |||||
(me.df.change || me.df.onchange).apply(this, [e]); | |||||
return; | |||||
} | |||||
if(me.doctype && me.docname && me.get_value) { | |||||
me.parse_validate_and_set_in_model(me.get_value()); | |||||
} else { | |||||
// inline | |||||
var value = me.get_value(); | |||||
var parsed = me.parse ? me.parse(value) : value; | |||||
var set_input = function(before, after) { | |||||
if(before !== after) { | |||||
me.set_input(after); | |||||
} | |||||
if(me.doc) { | |||||
me.doc[me.df.fieldname] = value; | |||||
} | |||||
me.set_mandatory && me.set_mandatory(after); | |||||
if(me.after_validate) { | |||||
me.after_validate(after, me.$input); | |||||
} | |||||
} | |||||
if(me.validate) { | |||||
me.validate(parsed, function(validated) { | |||||
set_input(value, validated); | |||||
}); | |||||
} else { | |||||
set_input(value, parsed); | |||||
} | |||||
} | |||||
me.parse_validate_and_set_in_model(me.get_value(), e); | |||||
}); | }); | ||||
}, | }, | ||||
bind_focusout: function() { | bind_focusout: function() { | ||||
@@ -1430,22 +1404,8 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ | |||||
return; | return; | ||||
} | } | ||||
var value = me.get_value(); | var value = me.get_value(); | ||||
if(me.doctype && me.docname) { | |||||
if(value!==me.last_value) { | |||||
me.parse_validate_and_set_in_model(value); | |||||
} | |||||
} else { | |||||
var cache_list = me.$input.cache[me.get_options()]; | |||||
if (cache_list && cache_list[""]) { | |||||
var docs = cache_list[""].map(item => item.label); | |||||
if(docs.includes(value)) { | |||||
me.set_mandatory(value); | |||||
} else { | |||||
me.$input.val(""); | |||||
} | |||||
} else { | |||||
me.$input.val(value); | |||||
} | |||||
if(value!==me.last_value) { | |||||
me.parse_validate_and_set_in_model(value); | |||||
} | } | ||||
}); | }); | ||||
@@ -1487,17 +1447,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ | |||||
frappe.boot.user.last_selected_values[me.df.options] = item.value; | frappe.boot.user.last_selected_values[me.df.options] = item.value; | ||||
} | } | ||||
if(me.frm && me.frm.doc) { | |||||
me.selected = true; | |||||
me.parse_validate_and_set_in_model(item.value); | |||||
setTimeout(function() { | |||||
me.selected = false; | |||||
}, 100); | |||||
} else { | |||||
me.$input.val(item.value); | |||||
me.$input.trigger("change"); | |||||
me.set_mandatory(item.value); | |||||
} | |||||
me.parse_validate_and_set_in_model(item.value); | |||||
}); | }); | ||||
this.$input.on("awesomplete-selectcomplete", function(e) { | this.$input.on("awesomplete-selectcomplete", function(e) { | ||||
@@ -1585,11 +1535,49 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ | |||||
return; | return; | ||||
} | } | ||||
if(this.frm) { | |||||
this.frm.script_manager.validate_link_and_fetch(this.df, this.get_options(), | |||||
this.docname, value, callback); | |||||
this.validate_link_and_fetch(this.df, this.get_options(), | |||||
this.docname, value, callback); | |||||
}, | |||||
validate_link_and_fetch: function(df, doctype, docname, value, callback) { | |||||
var me = this; | |||||
if(value) { | |||||
var fetch = ''; | |||||
if(this.frm && this.frm.fetch_dict[df.fieldname]) { | |||||
fetch = this.frm.fetch_dict[df.fieldname].columns.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, r.fetch_values); | |||||
} | |||||
if(callback) callback(r.valid_value); | |||||
} else { | |||||
if(callback) callback(""); | |||||
} | |||||
} | |||||
}); | |||||
} else if(callback) { | |||||
callback(value); | |||||
} | } | ||||
}, | }, | ||||
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); | |||||
} | |||||
} | |||||
}); | }); | ||||
if(Awesomplete) { | if(Awesomplete) { | ||||
@@ -169,7 +169,7 @@ frappe.ui.form.Grid = Class.extend({ | |||||
this.truncate_rows(data); | this.truncate_rows(data); | ||||
this.grid_rows_by_docname = {}; | this.grid_rows_by_docname = {}; | ||||
for(var ri=0;ri < data.length; ri++) { | |||||
for(var ri=0; ri < data.length; ri++) { | |||||
var d = data[ri]; | var d = data[ri]; | ||||
if(d.idx===undefined) { | if(d.idx===undefined) { | ||||
@@ -951,7 +951,6 @@ frappe.ui.form.GridRow = Class.extend({ | |||||
parent = column.field_area, | parent = column.field_area, | ||||
df = column.df; | df = column.df; | ||||
// no text editor in grid | // no text editor in grid | ||||
if (df.fieldtype=='Text Editor') { | if (df.fieldtype=='Text Editor') { | ||||
df.fieldtype = 'Text'; | df.fieldtype = 'Text'; | ||||
@@ -966,6 +965,8 @@ frappe.ui.form.GridRow = Class.extend({ | |||||
doctype: this.doc.doctype, | doctype: this.doc.doctype, | ||||
docname: this.doc.name, | docname: this.doc.name, | ||||
frm: this.grid.frm, | frm: this.grid.frm, | ||||
grid: this.grid, | |||||
grid_row: this, | |||||
value: this.doc[df.fieldname] | value: this.doc[df.fieldname] | ||||
}); | }); | ||||
@@ -103,9 +103,11 @@ frappe.ui.form.LinkedWith = class LinkedWith { | |||||
var me = this; | var me = this; | ||||
let links = null; | let links = null; | ||||
links = | |||||
Object.keys(this.frm.__linked_doctypes) | |||||
.filter(frappe.model.can_get_report); | |||||
if (this.frm.__linked_doctypes) { | |||||
links = | |||||
Object.keys(this.frm.__linked_doctypes) | |||||
.filter(frappe.model.can_get_report); | |||||
} | |||||
let flag; | let flag; | ||||
if(!links) { | if(!links) { | ||||
@@ -150,44 +150,6 @@ frappe.ui.form.ScriptManager = Class.extend({ | |||||
console.log("----- end of error message -----"); | console.log("----- end of error message -----"); | ||||
console.group && console.groupEnd(); | console.group && console.groupEnd(); | ||||
}, | }, | ||||
validate_link_and_fetch: function(df, doctype, docname, value, callback) { | |||||
var me = this; | |||||
if(value) { | |||||
var fetch = ''; | |||||
if(this.frm && this.frm.fetch_dict[df.fieldname]) | |||||
fetch = this.frm.fetch_dict[df.fieldname].columns.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) | |||||
me.set_fetch_values(df, docname, r.fetch_values); | |||||
if(callback) callback(r.valid_value); | |||||
} else { | |||||
if(callback) callback(""); | |||||
} | |||||
} | |||||
}); | |||||
} else if(callback) { | |||||
callback(value); | |||||
} | |||||
}, | |||||
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); | |||||
} | |||||
}, | |||||
copy_from_first_row: function(parentfield, current_row, fieldnames) { | copy_from_first_row: function(parentfield, current_row, fieldnames) { | ||||
var data = this.frm.doc[parentfield]; | var data = this.frm.doc[parentfield]; | ||||
if(data.length===1 || data[0]===current_row) return; | if(data.length===1 || data[0]===current_row) return; | ||||
@@ -252,6 +252,8 @@ frappe.views.Calendar = Class.extend({ | |||||
// see event_calendar.js | // see event_calendar.js | ||||
color = d.color; | color = d.color; | ||||
} | } | ||||
if(!color) color = "blue"; | |||||
d.className = "fc-bg-" + color; | d.className = "fc-bg-" + color; | ||||
return d; | return d; | ||||
}); | }); | ||||
@@ -315,21 +315,18 @@ frappe.views.QueryReport = Class.extend({ | |||||
if(df.get_query) f.get_query = df.get_query; | if(df.get_query) f.get_query = df.get_query; | ||||
if(df.on_change) f.on_change = df.on_change; | if(df.on_change) f.on_change = df.on_change; | ||||
// run report on change | |||||
f.$input.on("change", function() { | |||||
df.onchange = () => { | |||||
if(!me.flags.filters_set) { | if(!me.flags.filters_set) { | ||||
// don't trigger change while setting filters | // don't trigger change while setting filters | ||||
return; | return; | ||||
} | } | ||||
f.$input.blur(); | |||||
if (f.on_change) { | if (f.on_change) { | ||||
f.on_change(me); | f.on_change(me); | ||||
} else { | } else { | ||||
me.trigger_refresh(); | me.trigger_refresh(); | ||||
} | } | ||||
f.set_mandatory && f.set_mandatory(f.$input.val()); | f.set_mandatory && f.set_mandatory(f.$input.val()); | ||||
}); | |||||
} | |||||
} | } | ||||
}); | }); | ||||
@@ -416,6 +413,7 @@ frappe.views.QueryReport = Class.extend({ | |||||
return; | return; | ||||
} | } | ||||
}); | }); | ||||
if (!missing) { | if (!missing) { | ||||
me.refresh(); | me.refresh(); | ||||
} | } | ||||
@@ -63,6 +63,16 @@ | |||||
} | } | ||||
} | } | ||||
.avatar-xl { | |||||
margin-right: 10px; | |||||
width: 108px; | |||||
height: 108px; | |||||
.standard-image { | |||||
font-size: 72px; | |||||
} | |||||
} | |||||
.avatar-xs { | .avatar-xs { | ||||
margin-right: 3px; | margin-right: 3px; | ||||
margin-top: -2px; | margin-top: -2px; | ||||
@@ -6,7 +6,7 @@ httplib2 | |||||
jinja2 | jinja2 | ||||
markdown2 | markdown2 | ||||
markupsafe | markupsafe | ||||
mysqlclient==1.3.8 | |||||
mysqlclient==1.3.10 | |||||
python-geoip | python-geoip | ||||
python-geoip-geolite2 | python-geoip-geolite2 | ||||
python-dateutil | python-dateutil | ||||
@@ -41,4 +41,4 @@ xlwt | |||||
oauthlib | oauthlib | ||||
PyJWT | PyJWT | ||||
pypdf | pypdf | ||||
openpyxl | |||||
openpyxl |