From 525f1656ad82a71be118ca6d6821f78e85c639ee Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 1 Mar 2022 14:41:40 +0530 Subject: [PATCH 01/12] fix: Double signature in composed Email Re-do of https://github.com/frappe/frappe/pull/12520 Undone by https://github.com/frappe/frappe/pull/12878 Changes done to reflect current state of version-13 Co-authored-by: Suraj Shetty --- frappe/public/js/frappe/views/communication.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 1d219a7044..4cdc75e8cd 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -750,7 +750,7 @@ frappe.views.CommunicationComposer = class { signature = signature.replace(/\n/g, "
"); } - return "
" + signature; + return "
" + signature; } get_earlier_reply() { From da2dbfaae987f0646113d3b3c644a7991ff09bda Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 1 Mar 2022 22:58:41 +0530 Subject: [PATCH 02/12] refactor!: Deprecate ignore_permissions & flags in communication.make API --- frappe/core/doctype/communication/email.py | 116 ++++++++++++++---- .../doctype/notification/notification.py | 7 +- 2 files changed, 99 insertions(+), 24 deletions(-) diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 46ef7bf5d2..6a71697d37 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -22,12 +22,30 @@ OUTGOING_EMAIL_ACCOUNT_MISSING = _(""" @frappe.whitelist() -def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", - sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, - print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None, - flags=None, read_receipt=None, print_letterhead=True, email_template=None, communication_type=None, - ignore_permissions=False) -> Dict[str, str]: - """Make a new communication. +def make( + doctype=None, + name=None, + content=None, + subject=None, + sent_or_received="Sent", + sender=None, + sender_full_name=None, + recipients=None, + communication_medium="Email", + send_email=False, + print_html=None, + print_format=None, + attachments="[]", + send_me_a_copy=False, + cc=None, + bcc=None, + read_receipt=None, + print_letterhead=True, + email_template=None, + communication_type=None, + **kwargs, +) -> Dict[str, str]: + """Make a new communication. Checks for email permissions for specified Document. :param doctype: Reference DocType. :param name: Reference Document name. @@ -44,17 +62,69 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = :param send_me_a_copy: Send a copy to the sender (default **False**). :param email_template: Template which is used to compose mail . """ - is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report") - send_me_a_copy = cint(send_me_a_copy) + if kwargs: + from frappe.utils.commands import warn + warn( + f"Options {kwargs} used in frappe.core.doctype.communication.email.make " + "are deprecated or unsupported", + category=DeprecationWarning + ) + + if doctype and name and not frappe.has_permission(doctype=doctype, ptype="email", doc=name): + raise frappe.PermissionError( + f"You are not allowed to send emails related to: {doctype} {name}" + ) + + return _make( + doctype=doctype, + name=name, + content=content, + subject=subject, + sent_or_received=sent_or_received, + sender=sender, + sender_full_name=sender_full_name, + recipients=recipients, + communication_medium=communication_medium, + send_email=send_email, + print_html=print_html, + print_format=print_format, + attachments=attachments, + send_me_a_copy=cint(send_me_a_copy), + cc=cc, + bcc=bcc, + read_receipt=read_receipt, + print_letterhead=print_letterhead, + email_template=email_template, + communication_type=communication_type, + ) - if not ignore_permissions: - if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'): - raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format( - doctype=doctype, name=name)) - if not sender: - sender = get_formatted_email(frappe.session.user) +def _make( + doctype=None, + name=None, + content=None, + subject=None, + sent_or_received="Sent", + sender=None, + sender_full_name=None, + recipients=None, + communication_medium="Email", + send_email=False, + print_html=None, + print_format=None, + attachments="[]", + send_me_a_copy=False, + cc=None, + bcc=None, + read_receipt=None, + print_letterhead=True, + email_template=None, + communication_type=None, +) -> Dict[str, str]: + """Internal method to make a new communication that ignores Permission checks. + """ + sender = sender or get_formatted_email(frappe.session.user) recipients = list_to_str(recipients) if isinstance(recipients, list) else recipients cc = list_to_str(cc) if isinstance(cc, list) else cc bcc = list_to_str(bcc) if isinstance(bcc, list) else bcc @@ -87,17 +157,21 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = if cint(send_email): if not comm.get_outgoing_email_account(): - frappe.throw(msg=OUTGOING_EMAIL_ACCOUNT_MISSING, exc=frappe.OutgoingEmailError) + frappe.throw( + msg=OUTGOING_EMAIL_ACCOUNT_MISSING, exc=frappe.OutgoingEmailError + ) - comm.send_email(print_html=print_html, print_format=print_format, - send_me_a_copy=send_me_a_copy, print_letterhead=print_letterhead) + comm.send_email( + print_html=print_html, + print_format=print_format, + send_me_a_copy=send_me_a_copy, + print_letterhead=print_letterhead, + ) emails_not_sent_to = comm.exclude_emails_list(include_sender=send_me_a_copy) - return { - "name": comm.name, - "emails_not_sent_to": ", ".join(emails_not_sent_to) - } + return {"name": comm.name, "emails_not_sent_to": ", ".join(emails_not_sent_to)} + def validate_email(doc: "Communication") -> None: """Validate Email Addresses of Recipients and CC""" diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 2b62530847..bad32fb68f 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -186,7 +186,7 @@ def get_context(context): def send_an_email(self, doc, context): from email.utils import formataddr - from frappe.core.doctype.communication.email import make as make_communication + from frappe.core.doctype.communication.email import _make as make_communication subject = self.subject if "{" in subject: subject = frappe.render_template(self.subject, context) @@ -216,7 +216,8 @@ def get_context(context): # Add mail notification to communication list # No need to add if it is already a communication. if doc.doctype != 'Communication': - make_communication(doctype=doc.doctype, + make_communication( + doctype=doc.doctype, name=doc.name, content=message, subject=subject, @@ -228,7 +229,7 @@ def get_context(context): cc=cc, bcc=bcc, communication_type='Automated Message', - ignore_permissions=True) + ) def send_a_slack_msg(self, doc, context): send_slack_message( From 0ef99c3886020b8bb013ff20ce160bb0b69f5fb6 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 2 Mar 2022 18:51:30 +0530 Subject: [PATCH 03/12] fix: Add signature to Communication.content if not already added This fix adds a signature forcibly if found under the sender's User.email_signature or default outgoing email account's signature field. The previous method of adding a comment into the Email didn't work since Quill would discard comments before setting them. Adding signatures in get_formatted_html didn't seem apt since it's used in QueueBuilder to re-construct the Email before processing the Email Queue. This meant that the email content that was added in the Communication record would not be final. Now, we treat the signature as part of the Communication content. --- .../doctype/communication/communication.py | 38 +++++++++++++++++++ frappe/email/email_body.py | 8 +--- .../public/js/frappe/views/communication.js | 2 +- frappe/templates/emails/standard.html | 1 - requirements.txt | 1 + 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index f89f0d8765..759557ae4c 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -18,6 +18,7 @@ from urllib.parse import unquote from frappe.utils.user import is_system_user from frappe.contacts.doctype.contact.contact import get_contact_name from frappe.automation.doctype.assignment_rule.assignment_rule import apply as apply_assignment_rule +from parse import compile exclude_from_linked_with = True @@ -114,6 +115,43 @@ class Communication(Document, CommunicationEmailMixin): frappe.publish_realtime('new_message', self.as_dict(), user=self.reference_name, after_commit=True) + def set_signature_in_email_content(self): + """Set sender's User.email_signature or default outgoing's EmailAccount.signature to the email + """ + if not self.content: + return + + quill_parser = compile('
{}
') + email_body = quill_parser.parse(self.content) + + if not email_body: + return + + email_body = email_body[0] + + user_email_signature = frappe.db.get_value( + "User", + self.sender, + "email_signature", + ) if self.sender else None + + signature = user_email_signature or frappe.db.get_value( + "Email Account", + {"default_outgoing": 1, "add_signature": 1}, + "signature", + ) + + if not signature: + return + + _signature = quill_parser.parse(signature)[0] if "ql-editor" in signature else None + + if (_signature or signature) not in self.content: + self.content = f'{self.content}


{signature}' + + def before_save(self): + self.set_signature_in_email_content() + def on_update(self): # add to _comment property of the doctype, so it shows up in # comments count for the list view diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index c25e996bd3..0f45e42aac 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -259,17 +259,12 @@ def get_formatted_html(subject, message, footer=None, print_html=None, email_account = email_account or EmailAccount.find_outgoing(match_by_email=sender) - signature = None - if "" not in message: - signature = get_signature(email_account) - rendered_email = frappe.get_template("templates/emails/standard.html").render({ "brand_logo": get_brand_logo(email_account) if with_container or header else None, "with_container": with_container, "site_url": get_url(), "header": get_header(header), "content": message, - "signature": signature, "footer": get_footer(email_account, footer), "title": subject, "print_html": print_html, @@ -281,8 +276,7 @@ def get_formatted_html(subject, message, footer=None, print_html=None, if unsubscribe_link: html = html.replace("", unsubscribe_link.html) - html = inline_style_in_html(html) - return html + return inline_style_in_html(html) @frappe.whitelist() def get_email_html(template, args, subject, header=None, with_container=False): diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 4cdc75e8cd..1d219a7044 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -750,7 +750,7 @@ frappe.views.CommunicationComposer = class { signature = signature.replace(/\n/g, "
"); } - return "
" + signature; + return "
" + signature; } get_earlier_reply() { diff --git a/frappe/templates/emails/standard.html b/frappe/templates/emails/standard.html index 4a47c9cf90..2a2093e1e9 100644 --- a/frappe/templates/emails/standard.html +++ b/frappe/templates/emails/standard.html @@ -37,7 +37,6 @@

{{ content }}

-

{{ signature }}

diff --git a/requirements.txt b/requirements.txt index ba4a1a598b..c77ab1d424 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,6 +29,7 @@ maxminddb-geolite2==2018.703 num2words~=0.5.10 oauthlib~=3.1.0 openpyxl~=3.0.7 +parse~=1.19.0 passlib~=1.7.4 paytmchecksum~=1.7.0 pdfkit~=0.6.1 From 89a7dac300deb85d3f6aa080c5460f79650a5b6b Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 3 Mar 2022 19:38:34 +0530 Subject: [PATCH 04/12] fix: Add signature only if not Communication.flags.skip_add_signature This is added to handle purposely removed signature or custom signatures added via the Email Composer or via the email.make API --- frappe/core/doctype/communication/communication.py | 3 ++- frappe/core/doctype/communication/email.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 759557ae4c..475762f39d 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -150,7 +150,8 @@ class Communication(Document, CommunicationEmailMixin): self.content = f'{self.content}


{signature}' def before_save(self): - self.set_signature_in_email_content() + if not self.flags.skip_add_signature: + self.set_signature_in_email_content() def on_update(self): # add to _comment property of the doctype, so it shows up in diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 6a71697d37..195518f40d 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -147,7 +147,9 @@ def _make( "read_receipt":read_receipt, "has_attachment": 1 if attachments else 0, "communication_type": communication_type, - }).insert(ignore_permissions=True) + }) + comm.flags.skip_add_signature = True + comm.insert(ignore_permissions=True) # if not committed, delayed task doesn't find the communication if attachments: From 6edb1f09e455a72512f23fafaa2860854203d31d Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 7 Mar 2022 15:02:20 +0530 Subject: [PATCH 05/12] refactor: Fetch Linked Documents in single request ;) This action was broken into some heavy UI stuff that seems to be handled from the current UI from what I can tell and a request triggered from the Desk for every suspected linked document (talk about self DOS xD) Removed long dead with_doctype option in get_linked_docs API. How do i know it's been long dead? I've not come across code that won't very obviously break when hit with any non None value. --- frappe/desk/form/linked_with.py | 22 ++-- frappe/public/js/frappe/form/linked_with.js | 107 ++------------------ 2 files changed, 17 insertions(+), 112 deletions(-) diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index 572d3f2a94..76900565c9 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -3,7 +3,7 @@ import json from collections import defaultdict import itertools -from typing import List +from typing import Dict, List import frappe import frappe.desk.form.load @@ -367,7 +367,7 @@ def get_exempted_doctypes(): @frappe.whitelist() -def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): +def get_linked_docs(doctype: str, name: str, linkinfo: Dict = None) -> Dict[str, List]: if isinstance(linkinfo, str): # additional fields are added in linkinfo linkinfo = json.loads(linkinfo) @@ -377,23 +377,12 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): if not linkinfo: return results - if for_doctype: - links = frappe.get_doc(doctype, name).get_link_filters(for_doctype) - - if links: - linkinfo = links - - if for_doctype in linkinfo: - # only get linked with for this particular doctype - linkinfo = { for_doctype: linkinfo.get(for_doctype) } - else: - return results - for dt, link in linkinfo.items(): filters = [] link["doctype"] = dt link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt) linkmeta = link_meta_bundle[0] + if not linkmeta.get("issingle"): fields = [d.fieldname for d in linkmeta.get("fields", { "in_list_view": 1, @@ -456,6 +445,11 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): return results +@frappe.whitelist() +def get(doctype, docname): + linked_doctypes = get_linked_doctypes(doctype=doctype) + return get_linked_docs(doctype=doctype, name=docname, linkinfo=linked_doctypes) + @frappe.whitelist() def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False): """add list of doctypes this doctype is 'linked' with. diff --git a/frappe/public/js/frappe/form/linked_with.js b/frappe/public/js/frappe/form/linked_with.js index 20db7bdb7c..c47a6e0c86 100644 --- a/frappe/public/js/frappe/form/linked_with.js +++ b/frappe/public/js/frappe/form/linked_with.js @@ -1,9 +1,8 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// MIT License. See license.txt +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See LICENSE frappe.ui.form.LinkedWith = class LinkedWith { - constructor(opts) { $.extend(this, opts); } @@ -21,29 +20,23 @@ frappe.ui.form.LinkedWith = class LinkedWith { } make_dialog() { - this.dialog = new frappe.ui.Dialog({ title: __("Linked With") }); this.dialog.on_page_show = () => { - // execute ajax calls sequentially - // 1. get linked doctypes - // 2. load all doctypes - // 3. load linked docs - this.get_linked_doctypes() - .then(() => this.load_doctypes()) - .then(() => this.links_not_permitted_or_missing()) - .then(() => this.get_linked_docs()) - .then(() => this.make_html()); + frappe.xcall( + "frappe.desk.form.linked_with.get", + {"doctype": cur_frm.doctype, "docname": cur_frm.docname}, + ).then(r => { + this.frm.__linked_docs = r; + }).then(() => this.make_html()); }; } make_html() { - const linked_docs = this.frm.__linked_docs; - let html = ''; - + const linked_docs = this.frm.__linked_docs; const linked_doctypes = Object.keys(linked_docs); if (linked_doctypes.length === 0) { @@ -63,88 +56,6 @@ frappe.ui.form.LinkedWith = class LinkedWith { $(this.dialog.body).html(html); } - load_doctypes() { - const already_loaded = Object.keys(locals.DocType); - let doctypes_to_load = []; - - if (this.frm.__linked_doctypes) { - doctypes_to_load = - Object.keys(this.frm.__linked_doctypes) - .filter(doctype => !already_loaded.includes(doctype)); - } - - // load all doctypes asynchronously using with_doctype - const promises = doctypes_to_load.map(dt => { - return frappe.model.with_doctype(dt, () => { - if(frappe.listview_settings[dt]) { - // add additional fields to __linked_doctypes - this.frm.__linked_doctypes[dt].add_fields = - frappe.listview_settings[dt].add_fields; - } - }); - }); - - return Promise.all(promises); - } - - links_not_permitted_or_missing() { - let links = null; - - if (this.frm.__linked_doctypes) { - links = - Object.keys(this.frm.__linked_doctypes) - .filter(frappe.model.can_get_report); - } - - let flag; - if(!links) { - $(this.dialog.body).html(`${this.frm.__linked_doctypes - ? __("Not enough permission to see links") - : __("Not Linked to any record")}`); - flag = true; - } - flag = false; - - // reject Promise if not_permitted or missing - return new Promise( - (resolve, reject) => flag ? reject() : resolve() - ); - } - - get_linked_doctypes() { - return new Promise((resolve) => { - if (this.frm.__linked_doctypes) { - resolve(); - } - - frappe.call({ - method: "frappe.desk.form.linked_with.get_linked_doctypes", - args: { - doctype: this.frm.doctype - }, - callback: (r) => { - this.frm.__linked_doctypes = r.message; - resolve(); - } - }); - }); - } - - get_linked_docs() { - return frappe.call({ - method: "frappe.desk.form.linked_with.get_linked_docs", - args: { - doctype: this.frm.doctype, - name: this.frm.docname, - linkinfo: this.frm.__linked_doctypes, - for_doctype: this.for_doctype - }, - callback: (r) => { - this.frm.__linked_docs = r.message || {}; - } - }); - } - make_doc_head(heading) { return `

From 38fbe76ebff363ae1c49df7fb7b307eed0dda013 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 7 Mar 2022 15:44:30 +0530 Subject: [PATCH 06/12] fix: Eliminate broken & impermissible links from get_linked_docs --- frappe/desk/form/linked_with.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index 76900565c9..162707239a 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -380,9 +380,15 @@ def get_linked_docs(doctype: str, name: str, linkinfo: Dict = None) -> Dict[str, for dt, link in linkinfo.items(): filters = [] link["doctype"] = dt - link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt) + try: + link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt) + except Exception: + continue linkmeta = link_meta_bundle[0] + if not linkmeta.has_permission(): + continue + if not linkmeta.get("issingle"): fields = [d.fieldname for d in linkmeta.get("fields", { "in_list_view": 1, From 39fc90cb5bc53e58840605f64a31ea44b963a3d5 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 7 Mar 2022 16:35:53 +0530 Subject: [PATCH 07/12] fix(ux): Pop from message_log if DoesNotExistError --- frappe/desk/form/linked_with.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index 162707239a..010d65c95b 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -1,9 +1,10 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE + import json from collections import defaultdict import itertools -from typing import Dict, List +from typing import Dict, List, Optional import frappe import frappe.desk.form.load @@ -367,7 +368,7 @@ def get_exempted_doctypes(): @frappe.whitelist() -def get_linked_docs(doctype: str, name: str, linkinfo: Dict = None) -> Dict[str, List]: +def get_linked_docs(doctype: str, name: str, linkinfo: Optional[Dict] = None) -> Dict[str, List]: if isinstance(linkinfo, str): # additional fields are added in linkinfo linkinfo = json.loads(linkinfo) @@ -382,7 +383,10 @@ def get_linked_docs(doctype: str, name: str, linkinfo: Dict = None) -> Dict[str, link["doctype"] = dt try: link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt) - except Exception: + except Exception as e: + if isinstance(e, frappe.DoesNotExistError): + if frappe.local.message_log: + frappe.local.message_log.pop() continue linkmeta = link_meta_bundle[0] @@ -451,11 +455,13 @@ def get_linked_docs(doctype: str, name: str, linkinfo: Dict = None) -> Dict[str, return results + @frappe.whitelist() def get(doctype, docname): linked_doctypes = get_linked_doctypes(doctype=doctype) return get_linked_docs(doctype=doctype, name=docname, linkinfo=linked_doctypes) + @frappe.whitelist() def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False): """add list of doctypes this doctype is 'linked' with. @@ -470,6 +476,7 @@ def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False): else: return frappe.cache().hget("linked_doctypes", doctype, lambda: _get_linked_doctypes(doctype)) + def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False): ret = {} # find fields where this doctype is linked @@ -499,6 +506,7 @@ def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False) return ret + def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False): filters = [['fieldtype','=', 'Link'], ['options', '=', doctype]] @@ -529,6 +537,7 @@ def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False): return ret + def get_dynamic_linked_fields(doctype, without_ignore_user_permissions_enabled=False): ret = {} From 3858eff51e328f33e6eb782c609d347d86120840 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 7 Mar 2022 18:17:31 +0530 Subject: [PATCH 08/12] fix: Don't add signature only when called from API --- frappe/core/doctype/communication/email.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 195518f40d..b51749ccb7 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -96,6 +96,7 @@ def make( print_letterhead=print_letterhead, email_template=email_template, communication_type=communication_type, + add_signature=False, ) @@ -120,6 +121,7 @@ def _make( print_letterhead=True, email_template=None, communication_type=None, + add_signature=True, ) -> Dict[str, str]: """Internal method to make a new communication that ignores Permission checks. """ @@ -148,7 +150,7 @@ def _make( "has_attachment": 1 if attachments else 0, "communication_type": communication_type, }) - comm.flags.skip_add_signature = True + comm.flags.skip_add_signature = not add_signature comm.insert(ignore_permissions=True) # if not committed, delayed task doesn't find the communication From ce77e97db65a7ae2192e75a8da6c64191d76c0ee Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 8 Mar 2022 11:16:59 +0000 Subject: [PATCH 09/12] feat: enable tls for ngrok (#16216) * styled site module: sorted imports, space fixes --- frappe/commands/site.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) mode change 100755 => 100644 frappe/commands/site.py diff --git a/frappe/commands/site.py b/frappe/commands/site.py old mode 100755 new mode 100644 index c5d2257d75..b54f369e34 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -1,7 +1,7 @@ # imports - standard imports import os -import sys import shutil +import sys # imports - third party imports import click @@ -65,11 +65,11 @@ def restore(context, sql_file_path, encryption_key=None, db_root_username=None, "Restore site database from an sql file" from frappe.installer import ( _new_site, - extract_sql_from_archive, extract_files, + extract_sql_from_archive, is_downgrade, is_partial, - validate_database_sql + validate_database_sql, ) from frappe.utils.backups import Backup if not os.path.exists(sql_file_path): @@ -207,7 +207,7 @@ def restore(context, sql_file_path, encryption_key=None, db_root_username=None, @click.option('--encryption-key', help='Backup encryption key') @pass_context def partial_restore(context, sql_file_path, verbose, encryption_key=None): - from frappe.installer import partial_restore, extract_sql_from_archive + from frappe.installer import extract_sql_from_archive, partial_restore from frappe.utils.backups import Backup if not os.path.exists(sql_file_path): @@ -545,7 +545,7 @@ def _use(site, sites_path='.'): def use(site, sites_path='.'): if os.path.exists(os.path.join(sites_path, site)): - with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile: + with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile: sitefile.write(site) print("Current Site set to {}".format(site)) else: @@ -751,6 +751,7 @@ def set_admin_password(context, admin_password=None, logout_all_sessions=False): def set_user_password(site, user, password, logout_all_sessions=False): import getpass + from frappe.utils.password import update_password try: @@ -881,15 +882,16 @@ def stop_recording(context): raise SiteNotSpecifiedError @click.command('ngrok') +@click.option('--bind-tls', is_flag=True, default=False, help='Returns a reference to the https tunnel.') @pass_context -def start_ngrok(context): +def start_ngrok(context, bind_tls): from pyngrok import ngrok site = get_site(context) frappe.init(site=site) port = frappe.conf.http_port or frappe.conf.webserver_port - tunnel = ngrok.connect(addr=str(port), host_header=site) + tunnel = ngrok.connect(addr=str(port), host_header=site, bind_tls=bind_tls) print(f'Public URL: {tunnel.public_url}') print('Inspect logs at http://localhost:4040') From a6c257261b591b0b7882202e061732231d93a369 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 9 Mar 2022 12:16:14 +0530 Subject: [PATCH 10/12] fix(ux): remove attachment limits --- frappe/core/doctype/user/user.json | 3 +-- frappe/email/doctype/newsletter/newsletter.json | 3 +-- frappe/website/doctype/blog_post/blog_post.json | 3 +-- frappe/website/doctype/web_page/web_page.json | 3 +-- frappe/website/doctype/website_settings/website_settings.json | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index a47f539466..9e9529cd5e 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -668,8 +668,7 @@ "link_fieldname": "user" } ], - "max_attachments": 5, - "modified": "2022-01-03 11:53:25.250822", + "modified": "2022-03-09 01:47:56.745069", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json index baabd4991e..b42f4755cb 100644 --- a/frappe/email/doctype/newsletter/newsletter.json +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -236,8 +236,7 @@ "index_web_pages_for_search": 1, "is_published_field": "published", "links": [], - "max_attachments": 3, - "modified": "2021-12-06 20:09:37.963141", + "modified": "2022-03-09 01:48:16.741603", "modified_by": "Administrator", "module": "Email", "name": "Newsletter", diff --git a/frappe/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json index b05293f28b..5e3cc78d70 100644 --- a/frappe/website/doctype/blog_post/blog_post.json +++ b/frappe/website/doctype/blog_post/blog_post.json @@ -213,8 +213,7 @@ "index_web_pages_for_search": 1, "is_published_field": "published", "links": [], - "max_attachments": 5, - "modified": "2021-11-23 10:42:01.759723", + "modified": "2022-03-09 01:48:25.227295", "modified_by": "Administrator", "module": "Website", "name": "Blog Post", diff --git a/frappe/website/doctype/web_page/web_page.json b/frappe/website/doctype/web_page/web_page.json index b1fdd02af7..e7bd705272 100644 --- a/frappe/website/doctype/web_page/web_page.json +++ b/frappe/website/doctype/web_page/web_page.json @@ -338,8 +338,7 @@ "index_web_pages_for_search": 1, "is_published_field": "published", "links": [], - "max_attachments": 20, - "modified": "2022-01-03 13:01:48.182645", + "modified": "2022-03-09 01:45:28.548671", "modified_by": "Administrator", "module": "Website", "name": "Web Page", diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index 3b199a4b58..b628437315 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -420,8 +420,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "max_attachments": 10, - "modified": "2022-02-24 15:37:22.360138", + "modified": "2022-03-09 01:47:31.094462", "modified_by": "Administrator", "module": "Website", "name": "Website Settings", From e8ec8410e628806c0a54939d78b992d3582fc48f Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 9 Mar 2022 12:57:27 +0530 Subject: [PATCH 11/12] fix(rename_doc): Allow updating only document title or name --- frappe/model/rename_doc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index faa3859c91..b4a53e3131 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -43,8 +43,8 @@ def update_document_title( title_field = doc.meta.get_title_field() - title_updated = (title_field != "name") and (updated_title != doc.get(title_field)) - name_updated = updated_name != doc.name + title_updated = updated_title and (title_field != "name") and (updated_title != doc.get(title_field)) + name_updated = updated_name and (updated_name != doc.name) if name_updated: docname = rename_doc(doctype=doctype, old=docname, new=updated_name, merge=merge) From 9cd25d72d538f72038023c2c1dac04959e8c1a95 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 9 Mar 2022 18:50:18 +0530 Subject: [PATCH 12/12] fix: clear filters before setting them (#16077) * fix: clear filters before setting them * fix: dont set dirty state for reports without JSON * fix: cleaner alternative to clearing filters * fix: move condition outside loop * fix: reorder if-else block to avoid hitting else block inadvertently --- frappe/public/js/frappe/list/base_list.js | 16 ++++++++++---- frappe/public/js/frappe/list/list_view.js | 21 ++----------------- .../js/frappe/views/reports/report_view.js | 7 ++++--- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 75063cc53f..d5ee82acce 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -760,6 +760,10 @@ class FilterArea { const doctype_fields = this.list_view.meta.fields; const title_field = this.list_view.meta.title_field; + const has_existing_filters = ( + this.list_view.filters + && this.list_view.filters.length > 0 + ); fields = fields.concat( doctype_fields @@ -794,13 +798,17 @@ class FilterArea { options = options.join("\n"); } } - let default_value = - fieldtype === "Link" - ? frappe.defaults.get_user_default(options) - : null; + + let default_value; + + if (fieldtype === "Link" && !has_existing_filters) { + default_value = frappe.defaults.get_user_default(options); + } + if (["__default", "__global"].includes(default_value)) { default_value = null; } + return { fieldtype: fieldtype, label: __(df.label), diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 5cfc7c75d4..98553a4a3e 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -83,32 +83,15 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.sort_by = this.view_user_settings.sort_by || "modified"; this.sort_order = this.view_user_settings.sort_order || "desc"; - // set filters from user_settings or list_settings - if ( - this.view_user_settings.filters && - this.view_user_settings.filters.length - ) { - // Priority 1: user_settings - const saved_filters = this.view_user_settings.filters; - this.filters = this.validate_filters(saved_filters); - } else { - // Priority 2: filters in listview_settings - this.filters = (this.settings.filters || []).map((f) => { - if (f.length === 3) { - f = [this.doctype, f[0], f[1], f[2]]; - } - return f; - }); - } - // build menu items this.menu_items = this.menu_items.concat(this.get_menu_items()); + // set filters from view_user_settings or list_settings if ( this.view_user_settings.filters && this.view_user_settings.filters.length ) { - // Priority 1: saved filters + // Priority 1: view_user_settings const saved_filters = this.view_user_settings.filters; this.filters = this.validate_filters(saved_filters); } else { diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index d073f5c7f2..f80d59350f 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -125,11 +125,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { } after_render() { - if (this.report_doc) { - this.set_dirty_state_for_custom_report(); - } else { + if (!this.report_doc) { this.save_report_settings(); + } else if (!$.isEmptyObject(this.report_doc.json)) { + this.set_dirty_state_for_custom_report(); } + if (!this.group_by) { this.init_chart(); }