From cf42986d8873e61238827e84bef7c74df12d5a3e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 22 Apr 2021 16:44:59 +0530 Subject: [PATCH 01/34] fix(UI): dont disable dialog scroll on focusing a Link/Autocomplete field --- .../public/js/frappe/form/controls/autocomplete.js | 6 ------ frappe/public/js/frappe/form/controls/link.js | 6 ------ frappe/public/js/frappe/ui/filters/field_select.js | 12 ------------ 3 files changed, 24 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/autocomplete.js b/frappe/public/js/frappe/form/controls/autocomplete.js index 30eb277f08..7b5680f394 100644 --- a/frappe/public/js/frappe/form/controls/autocomplete.js +++ b/frappe/public/js/frappe/form/controls/autocomplete.js @@ -90,16 +90,10 @@ frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({ }); this.$input.on("awesomplete-open", () => { - this.toggle_container_scroll('.modal-dialog', 'modal-dialog-scrollable'); - this.toggle_container_scroll('.grid-form-body .form-area', 'scrollable'); - this.autocomplete_open = true; }); this.$input.on("awesomplete-close", () => { - this.toggle_container_scroll('.modal-dialog', 'modal-dialog-scrollable', true); - this.toggle_container_scroll('.grid-form-body .form-area', 'scrollable', true); - this.autocomplete_open = false; }); diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index c0ff128088..c32c99f0ed 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -241,16 +241,10 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ }); this.$input.on("awesomplete-open", () => { - this.toggle_container_scroll('.modal-dialog', 'modal-dialog-scrollable'); - this.toggle_container_scroll('.grid-form-body .form-area', 'scrollable'); - this.autocomplete_open = true; }); this.$input.on("awesomplete-close", () => { - this.toggle_container_scroll('.modal-dialog', 'modal-dialog-scrollable', true); - this.toggle_container_scroll('.grid-form-body .form-area', 'scrollable', true); - this.autocomplete_open = false; }); diff --git a/frappe/public/js/frappe/ui/filters/field_select.js b/frappe/public/js/frappe/ui/filters/field_select.js index c362214ce2..ed271a73aa 100644 --- a/frappe/public/js/frappe/ui/filters/field_select.js +++ b/frappe/public/js/frappe/ui/filters/field_select.js @@ -36,18 +36,6 @@ frappe.ui.FieldSelect = Class.extend({ var item = me.awesomplete.get_item(value); me.$input.val(item.label); }); - this.$input.on("awesomplete-open", () => { - let modal = this.$input.parents('.modal-dialog')[0]; - if (modal) { - $(modal).removeClass("modal-dialog-scrollable"); - } - }); - this.$input.on("awesomplete-close", () => { - let modal = this.$input.parents('.modal-dialog')[0]; - if (modal) { - $(modal).addClass("modal-dialog-scrollable"); - } - }); if(this.filter_fields) { for(var i in this.filter_fields) From d8d86f7498a6f20652d037541be64935755799e0 Mon Sep 17 00:00:00 2001 From: leela Date: Mon, 26 Apr 2021 06:24:03 +0530 Subject: [PATCH 02/34] refactor: enable profiler from env variable --- frappe/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/app.py b/frappe/app.py index 607479ad52..f17f1494b2 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -286,7 +286,7 @@ def serve(port=8000, profile=False, no_reload=False, no_threading=False, site=No from werkzeug.serving import run_simple - if profile: + if profile or os.environ.get('USE_PROFILER'): application = ProfilerMiddleware(application, sort_by=('cumtime', 'calls')) if not os.environ.get('NO_STATICS'): From a8f74d9471c5116017d7469e21190135e66d8494 Mon Sep 17 00:00:00 2001 From: leela Date: Tue, 27 Apr 2021 09:15:03 +0530 Subject: [PATCH 03/34] refactor: Move finding email accounts code to EmailAccount doctype --- frappe/app.py | 2 +- frappe/core/doctype/communication/email.py | 21 +-- .../doctype/email_account/email_account.py | 155 +++++++++++++++++- .../doctype/email_domain/test_records.json | 6 +- frappe/email/email_body.py | 11 +- frappe/email/queue.py | 9 +- frappe/email/smtp.py | 132 +-------------- frappe/email/test_smtp.py | 10 +- frappe/utils/error.py | 50 +++++- 9 files changed, 231 insertions(+), 165 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index f17f1494b2..5dbebb061d 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -184,7 +184,7 @@ def make_form_dict(request): args = request.form or request.args if not isinstance(args, dict): - frappe.throw("Invalid request arguments") + frappe.throw(_("Invalid request arguments")) try: frappe.local.form_dict = frappe._dict({ k:v[0] if isinstance(v, (list, tuple)) else v \ diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 731cb85d7c..d3017055cf 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -272,22 +272,13 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None) doc.attachments.append(a) def set_incoming_outgoing_accounts(doc): - doc.incoming_email_account = doc.outgoing_email_account = None + from frappe.email.doctype.email_account.email_account import EmailAccount + incoming_email_account = EmailAccount.find_incoming( + match_by_email=doc.sender, match_by_doctype=doc.reference_doctype) + doc.incoming_email_account = incoming_email_account.email_id if incoming_email_account else None - if not doc.incoming_email_account and doc.sender: - doc.incoming_email_account = frappe.db.get_value("Email Account", - {"email_id": doc.sender, "enable_incoming": 1}, "email_id") - - if not doc.incoming_email_account and doc.reference_doctype: - doc.incoming_email_account = frappe.db.get_value("Email Account", - {"append_to": doc.reference_doctype, }, "email_id") - - if not doc.incoming_email_account: - doc.incoming_email_account = frappe.db.get_value("Email Account", - {"default_incoming": 1, "enable_incoming": 1}, "email_id") - - doc.outgoing_email_account = frappe.email.smtp.get_outgoing_email_account(raise_exception_not_set=False, - append_to=doc.doctype, sender=doc.sender) + doc.outgoing_email_account = EmailAccount.find_outgoing( + match_by_email=doc.sender, match_by_doctype=doc.reference_doctype) if doc.sent_or_received == "Sent": doc.db_set("email_account", doc.outgoing_email_account.name) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 4869c5a9bf..3aa7c10ea5 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -8,9 +8,14 @@ import re import json import socket import time -from frappe import _ +import functools + +import email.utils + +from frappe import _, are_emails_muted from frappe.model.document import Document -from frappe.utils import validate_email_address, cint, cstr, get_datetime, DATE_FORMAT, strip, comma_or, sanitize_html, add_days +from frappe.utils import (validate_email_address, cint, cstr, get_datetime, + DATE_FORMAT, strip, comma_or, sanitize_html, add_days, parse_addr) from frappe.utils.user import is_system_user from frappe.utils.jinja import render_template from frappe.email.smtp import SMTPServer @@ -21,17 +26,40 @@ from datetime import datetime, timedelta from frappe.desk.form import assign_to from frappe.utils.user import get_system_managers from frappe.utils.background_jobs import enqueue, get_jobs -from frappe.core.doctype.communication.email import set_incoming_outgoing_accounts from frappe.utils.html_utils import clean_email_html +from frappe.utils.error import raise_error_on_no_output from frappe.email.utils import get_port +OUTGOING_EMAIL_ACCOUNT_MISSING = _("Please setup default Email Account from Setup > Email > Email Account") + class SentEmailInInbox(Exception): pass class InvalidEmailCredentials(frappe.ValidationError): pass +def cache_email_account(cache_name): + def decorator_cache_email_account(func): + @functools.wraps(func) + def wrapper_cache_email_account(*args, **kwargs): + if not hasattr(frappe.local, cache_name): + setattr(frappe.local, cache_name, {}) + + cached_accounts = getattr(frappe.local, cache_name) + match_by = list(kwargs.values()) + ['default'] + matched_accounts = list(filter(None, [cached_accounts.get(key) for key in match_by])) + if matched_accounts: + return matched_accounts[0] + + matched_accounts = func(*args, **kwargs) + cached_accounts.update(matched_accounts or {}) + return matched_accounts and list(matched_accounts.values())[0] + return wrapper_cache_email_account + return decorator_cache_email_account + class EmailAccount(Document): + DOCTYPE = 'Email Account' + def autoname(self): """Set name as `email_account_name` or make title from Email Address.""" if not self.email_account_name: @@ -249,6 +277,15 @@ class EmailAccount(Document): else: raise + @property + def _password(self): + raise_exception = not self.no_smtp_authentication + return self.get_password(raise_exception=raise_exception) + + @property + def default_sender(self): + return email.utils.formataddr((self.name, self.get("email_id"))) + @classmethod def throw_invalid_credentials_exception(cls): frappe.throw( @@ -257,6 +294,114 @@ class EmailAccount(Document): title=_("Invalid Credentials") ) + @classmethod + def from_record(cls, record): + email_account = frappe.new_doc(cls.DOCTYPE) + email_account.update(record) + return email_account + + @classmethod + def find(cls, name): + return frappe.get_doc(cls.DOCTYPE, name) + + @classmethod + def find_one_by_filters(cls, **kwargs): + name = frappe.db.get_value(cls.DOCTYPE, kwargs) + return cls.find(name) if name else None + + @classmethod + def find_from_config(cls): + config = cls.get_account_details_from_site_config() + return cls.from_record(config) if config else None + + @classmethod + def create_dummy(cls): + return cls.from_record({"sender": "notifications@example.com"}) + + @classmethod + @raise_error_on_no_output( + keep_quiet = lambda: not cint(frappe.get_system_settings('setup_complete')), + error_message = OUTGOING_EMAIL_ACCOUNT_MISSING, error_type = frappe.OutgoingEmailError) # noqa + @cache_email_account('outgoing_email_account') + def find_outgoing(cls, match_by_email=None, match_by_doctype=None, _raise_error=False): + """Find the outgoing Email account to use. + + :param match_by_email: Find account using emailID + :param match_by_doctype: Find account by matching `Append To` doctype + :param _raise_error: This is used by raise_error_on_no_output decorator to raise error. + """ + if match_by_email: + match_by_email = parse_addr(match_by_email)[1] + doc = cls.find_one_by_filters(enable_outgoing=1, email_id=match_by_email) + if doc: + return {match_by_email: doc} + + if match_by_doctype: + doc = cls.find_one_by_filters(enable_outgoing=1, enable_incoming=1, append_to=match_by_doctype) + if doc: + return {match_by_doctype: doc} + + doc = cls.find_default_outgoing() + if doc: + return {'default': doc} + + @classmethod + def find_default_outgoing(cls): + """ Find default outgoing account. + """ + doc = cls.find_one_by_filters(enable_outgoing=1, default_outgoing=1) + doc = doc or cls.find_from_config() + return doc or (are_emails_muted() and cls.create_dummy()) + + @classmethod + def find_incoming(cls, match_by_email=None, match_by_doctype=None): + """Find the incoming Email account to use. + :param match_by_email: Find account using emailID + :param match_by_doctype: Find account by matching `Append To` doctype + """ + doc = cls.find_one_by_filters(enable_incoming=1, email_id=match_by_email) + if doc: + return doc + + doc = cls.find_one_by_filters(enable_incoming=1, append_to=match_by_doctype) + if doc: + return doc + + doc = cls.find_default_incoming() + return doc + + @classmethod + def find_default_incoming(cls): + doc = cls.find_one_by_filters(enable_incoming=1, default_incoming=1) + return doc + + @classmethod + def get_account_details_from_site_config(cls): + if not frappe.conf.get("mail_server"): + return {} + + field_to_conf_name_map = { + 'smtp_server': {'conf_names': ('mail_server',)}, + 'smtp_port': {'conf_names': ('mail_port',)}, + 'use_tls': {'conf_names': ('use_tls', 'mail_login')}, + 'login_id': {'conf_names': ('mail_login',)}, + 'email_id': {'conf_names': ('auto_email_id', 'mail_login'), 'default': 'notifications@example.com'}, + 'password': {'conf_names': ('mail_password',)}, + 'always_use_account_email_id_as_sender': + {'conf_names': ('always_use_account_email_id_as_sender',), 'default': 0}, + 'always_use_account_name_as_sender_name': + {'conf_names': ('always_use_account_name_as_sender_name',), 'default': 0}, + 'name': {'conf_names': ('email_sender_name',), 'default': 'Frappe'}, + 'from_site_config': {'default': True} + } + + account_details = {} + for doc_field_name, d in field_to_conf_name_map.items(): + conf_names, default = d.get('conf_names') or [], d.get('default') + value = [frappe.conf.get(k) for k in conf_names if frappe.conf.get(k)] + account_details[doc_field_name] = (value and value[0]) or default + return account_details + def handle_incoming_connect_error(self, description): if test_internet(): if self.get_failed_attempts_count() > 2: @@ -642,6 +787,8 @@ class EmailAccount(Document): def send_auto_reply(self, communication, email): """Send auto reply if set.""" + from frappe.core.doctype.communication.email import set_incoming_outgoing_accounts + if self.enable_auto_reply: set_incoming_outgoing_accounts(communication) @@ -653,7 +800,7 @@ class EmailAccount(Document): frappe.sendmail(recipients = [email.from_email], sender = self.email_id, reply_to = communication.incoming_email_account, - subject = _("Re: ") + communication.subject, + subject = " ".join([_("Re:"), communication.subject]), content = render_template(self.auto_reply_message or "", communication.as_dict()) or \ frappe.get_template("templates/emails/auto_reply.html").render(communication.as_dict()), reference_doctype = communication.reference_doctype, diff --git a/frappe/email/doctype/email_domain/test_records.json b/frappe/email/doctype/email_domain/test_records.json index 32bc66e150..a6ccc99f06 100644 --- a/frappe/email/doctype/email_domain/test_records.json +++ b/frappe/email/doctype/email_domain/test_records.json @@ -10,7 +10,8 @@ "incoming_port": "993", "attachment_limit": "1", "smtp_server": "smtp.test.com", - "smtp_port": "587" + "smtp_port": "587", + "password": "password" }, { "doctype": "Email Account", @@ -25,6 +26,7 @@ "incoming_port": "143", "attachment_limit": "1", "smtp_server": "smtp.test.com", - "smtp_port": "587" + "smtp_port": "587", + "password": "password" } ] diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 3dcdf00a8e..45888119ea 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, re, os from frappe.utils.pdf import get_pdf -from frappe.email.smtp import get_outgoing_email_account +from frappe.email.doctype.email_account.email_account import EmailAccount from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint, split_emails, to_markdown, markdown, random_string, parse_addr) import email.utils @@ -75,7 +75,8 @@ class EMail: self.bcc = bcc or [] self.html_set = False - self.email_account = email_account or get_outgoing_email_account(sender=sender) + self.email_account = email_account or \ + EmailAccount.find_outgoing(match_by_email=sender, _raise_error=True) def set_html(self, message, text_content = None, footer=None, print_html=None, formatted=None, inline_images=None, header=None): @@ -249,8 +250,8 @@ class EMail: def get_formatted_html(subject, message, footer=None, print_html=None, email_account=None, header=None, unsubscribe_link=None, sender=None, with_container=False): - if not email_account: - email_account = get_outgoing_email_account(False, sender=sender) + + email_account = email_account or EmailAccount.find_outgoing(match_by_email=sender) signature = None if "" not in message: @@ -480,4 +481,4 @@ def sanitize_email_header(str): return str.replace('\r', '').replace('\n', '') def get_brand_logo(email_account): - return email_account.get('brand_logo') \ No newline at end of file + return email_account.get('brand_logo') diff --git a/frappe/email/queue.py b/frappe/email/queue.py index 2aff04edc9..cd984e9bf9 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -7,7 +7,8 @@ import sys from six.moves import html_parser as HTMLParser import smtplib, quopri, json from frappe import msgprint, _, safe_decode, safe_encode, enqueue -from frappe.email.smtp import SMTPServer, get_outgoing_email_account +from frappe.email.smtp import SMTPServer +from frappe.email.doctype.email_account.email_account import EmailAccount from frappe.email.email_body import get_email, get_formatted_html, add_attachment from frappe.utils.verified_command import get_signed_params, verify_request from html2text import html2text @@ -73,7 +74,9 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= if isinstance(send_after, int): send_after = add_days(nowdate(), send_after) - email_account = get_outgoing_email_account(True, append_to=reference_doctype, sender=sender) + email_account = EmailAccount.find_outgoing( + match_by_doctype=reference_doctype, match_by_email=sender, _raise_error=True) + if not sender or sender == "Administrator": sender = email_account.default_sender @@ -516,7 +519,7 @@ def prepare_message(email, recipient, recipients_list): return "" # Parse "Email Account" from "Email Sender" - email_account = get_outgoing_email_account(raise_exception_not_set=False, sender=email.sender) + email_account = EmailAccount.find_outgoing(match_by_email=email.sender) if frappe.conf.use_ssl and email_account.track_email_status: # Using SSL => Publically available domain => Email Read Reciept Possible message = message.replace("", quopri.encodestring(''.format(frappe.local.site, email.communication).encode()).decode()) diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py index 9ba81fa146..ca69e621cc 100644 --- a/frappe/email/smtp.py +++ b/frappe/email/smtp.py @@ -34,126 +34,6 @@ def send(email, append_to=None, retry=1): _send(retry) -def get_outgoing_email_account(raise_exception_not_set=True, append_to=None, sender=None): - """Returns outgoing email account based on `append_to` or the default - outgoing account. If default outgoing account is not found, it will - try getting settings from `site_config.json`.""" - - sender_email_id = None - _email_account = None - - if sender: - sender_email_id = parse_addr(sender)[1] - - if not getattr(frappe.local, "outgoing_email_account", None): - frappe.local.outgoing_email_account = {} - - if not (frappe.local.outgoing_email_account.get(append_to) - or frappe.local.outgoing_email_account.get(sender_email_id) - or frappe.local.outgoing_email_account.get("default")): - email_account = None - - if sender_email_id: - # check if the sender has an email account with enable_outgoing - email_account = _get_email_account({"enable_outgoing": 1, - "email_id": sender_email_id}) - - if not email_account and append_to: - # append_to is only valid when enable_incoming is checked - email_accounts = frappe.db.get_values("Email Account", { - "enable_outgoing": 1, - "enable_incoming": 1, - "append_to": append_to, - }, cache=True) - - if email_accounts: - _email_account = email_accounts[0] - - else: - email_account = _get_email_account({ - "enable_outgoing": 1, - "enable_incoming": 1, - "append_to": append_to - }) - - if not email_account: - # sender don't have the outging email account - sender_email_id = None - email_account = get_default_outgoing_email_account(raise_exception_not_set=raise_exception_not_set) - - if not email_account and _email_account: - # if default email account is not configured then setup first email account based on append to - email_account = _email_account - - if not email_account and raise_exception_not_set and cint(frappe.db.get_single_value('System Settings', 'setup_complete')): - frappe.throw(_("Please setup default Email Account from Setup > Email > Email Account"), - frappe.OutgoingEmailError) - - if email_account: - if email_account.enable_outgoing and not getattr(email_account, 'from_site_config', False): - raise_exception = True - if email_account.smtp_server in ['localhost','127.0.0.1'] or email_account.no_smtp_authentication: - raise_exception = False - email_account.password = email_account.get_password(raise_exception=raise_exception) - email_account.default_sender = email.utils.formataddr((email_account.name, email_account.get("email_id"))) - - frappe.local.outgoing_email_account[append_to or sender_email_id or "default"] = email_account - - return frappe.local.outgoing_email_account.get(append_to) \ - or frappe.local.outgoing_email_account.get(sender_email_id) \ - or frappe.local.outgoing_email_account.get("default") - -def get_default_outgoing_email_account(raise_exception_not_set=True): - '''conf should be like: - { - "mail_server": "smtp.example.com", - "mail_port": 587, - "use_tls": 1, - "mail_login": "emails@example.com", - "mail_password": "Super.Secret.Password", - "auto_email_id": "emails@example.com", - "email_sender_name": "Example Notifications", - "always_use_account_email_id_as_sender": 0, - "always_use_account_name_as_sender_name": 0 - } - ''' - email_account = _get_email_account({"enable_outgoing": 1, "default_outgoing": 1}) - if email_account: - email_account.password = email_account.get_password(raise_exception=False) - - if not email_account and frappe.conf.get("mail_server"): - # from site_config.json - email_account = frappe.new_doc("Email Account") - email_account.update({ - "smtp_server": frappe.conf.get("mail_server"), - "smtp_port": frappe.conf.get("mail_port"), - - # legacy: use_ssl was used in site_config instead of use_tls, but meant the same thing - "use_tls": cint(frappe.conf.get("use_tls") or 0) or cint(frappe.conf.get("use_ssl") or 0), - "login_id": frappe.conf.get("mail_login"), - "email_id": frappe.conf.get("auto_email_id") or frappe.conf.get("mail_login") or 'notifications@example.com', - "password": frappe.conf.get("mail_password"), - "always_use_account_email_id_as_sender": frappe.conf.get("always_use_account_email_id_as_sender", 0), - "always_use_account_name_as_sender_name": frappe.conf.get("always_use_account_name_as_sender_name", 0) - }) - email_account.from_site_config = True - email_account.name = frappe.conf.get("email_sender_name") or "Frappe" - - if not email_account and not raise_exception_not_set: - return None - - if frappe.are_emails_muted(): - # create a stub - email_account = frappe.new_doc("Email Account") - email_account.update({ - "email_id": "notifications@example.com" - }) - - return email_account - -def _get_email_account(filters): - name = frappe.db.get_value("Email Account", filters) - return frappe.get_doc("Email Account", name) if name else None class SMTPServer: def __init__(self, login=None, password=None, server=None, port=None, use_tls=None, use_ssl=None, append_to=None): @@ -176,17 +56,15 @@ class SMTPServer: self.setup_email_account(append_to) def setup_email_account(self, append_to=None, sender=None): - self.email_account = get_outgoing_email_account(raise_exception_not_set=False, append_to=append_to, sender=sender) + from frappe.email.doctype.email_account.email_account import EmailAccount + self.email_account = EmailAccount.find_outgoing(match_by_doctype=append_to, match_by_email=sender) if self.email_account: self.server = self.email_account.smtp_server self.login = (getattr(self.email_account, "login_id", None) or self.email_account.email_id) - if not self.email_account.no_smtp_authentication: - if self.email_account.ascii_encode_password: - self.password = frappe.safe_encode(self.email_account.password, 'ascii') - else: - self.password = self.email_account.password - else: + if self.email_account.no_smtp_authentication or frappe.local.flags.in_test: self.password = None + else: + self.password = self.email_account._password self.port = self.email_account.smtp_port self.use_tls = self.email_account.use_tls self.sender = self.email_account.email_id diff --git a/frappe/email/test_smtp.py b/frappe/email/test_smtp.py index 0b11c559a2..e170617383 100644 --- a/frappe/email/test_smtp.py +++ b/frappe/email/test_smtp.py @@ -4,7 +4,7 @@ import unittest import frappe from frappe.email.smtp import SMTPServer -from frappe.email.smtp import get_outgoing_email_account +from frappe.email.doctype.email_account.email_account import EmailAccount class TestSMTP(unittest.TestCase): def test_smtp_ssl_session(self): @@ -33,13 +33,13 @@ class TestSMTP(unittest.TestCase): frappe.local.outgoing_email_account = {} # lowest preference given to email account with default incoming enabled - create_email_account(email_id="default_outgoing_enabled@gmail.com", password="***", enable_outgoing = 1, default_outgoing=1) - self.assertEqual(get_outgoing_email_account().email_id, "default_outgoing_enabled@gmail.com") + create_email_account(email_id="default_outgoing_enabled@gmail.com", password="password", enable_outgoing = 1, default_outgoing=1) + self.assertEqual(EmailAccount.find_outgoing().email_id, "default_outgoing_enabled@gmail.com") frappe.local.outgoing_email_account = {} # highest preference given to email account with append_to matching - create_email_account(email_id="append_to@gmail.com", password="***", enable_outgoing = 1, default_outgoing=1, append_to="Blog Post") - self.assertEqual(get_outgoing_email_account(append_to="Blog Post").email_id, "append_to@gmail.com") + create_email_account(email_id="append_to@gmail.com", password="password", enable_outgoing = 1, default_outgoing=1, append_to="Blog Post") + self.assertEqual(EmailAccount.find_outgoing(match_by_doctype="Blog Post").email_id, "append_to@gmail.com") # add back the mail_server frappe.conf['mail_server'] = mail_server diff --git a/frappe/utils/error.py b/frappe/utils/error.py index d0e21a4188..2d8d6491a5 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -4,12 +4,14 @@ from __future__ import unicode_literals -import frappe -from frappe.utils import cstr, encode import os import sys -import inspect import traceback +import functools + +import frappe +from frappe.utils import cstr, encode +import inspect import linecache import pydoc import cgitb @@ -190,3 +192,45 @@ def clear_old_snapshots(): def get_error_snapshot_path(): return frappe.get_site_path('error-snapshots') + +def get_default_args(func): + """Get default arguments of a function from its signature. + """ + signature = inspect.signature(func) + return {k: v.default + for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty} + +def raise_error_on_no_output(error_message, error_type=None, keep_quiet=None): + """Decorate any function to throw error incase of missing output. + + TODO: Remove keep_quiet flag after testing and fixing sendmail flow. + + :param error_message: error message to raise + :param error_type: type of error to raise + :param keep_quiet: control error raising with external factor. + :type error_message: str + :type error_type: Exception Class + :type keep_quiet: function + + >>> @raise_error_on_no_output("Ingradients missing") + ... def get_indradients(_raise_error=1): return + ... + >>> get_indradients() + `Exception Name`: Ingradients missing + """ + def decorator_raise_error_on_no_output(func): + @functools.wraps(func) + def wrapper_raise_error_on_no_output(*args, **kwargs): + response = func(*args, **kwargs) + if callable(keep_quiet) and keep_quiet(): + return response + + default_kwargs = get_default_args(func) + default_raise_error = default_kwargs.get('_raise_error') + raise_error = kwargs.get('_raise_error') if '_raise_error' in kwargs else default_raise_error + + if (not response) and raise_error: + frappe.throw(error_message, error_type or Exception) + return response + return wrapper_raise_error_on_no_output + return decorator_raise_error_on_no_output From 0a1902e65063772640f90d77e999a055726c10e5 Mon Sep 17 00:00:00 2001 From: leela Date: Mon, 3 May 2021 06:25:57 +0530 Subject: [PATCH 04/34] fix: semgrep's split translation regex --- .github/helper/semgrep_rules/translate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml index 3737da5a7e..732c3daca5 100644 --- a/.github/helper/semgrep_rules/translate.yml +++ b/.github/helper/semgrep_rules/translate.yml @@ -42,7 +42,7 @@ rules: - id: frappe-translation-python-splitting pattern-either: - - pattern: _(...) + ... + _(...) + - pattern: _(...) + _(...) - pattern: _("..." + "...") - pattern-regex: '_\([^\)]*\\\s*' message: | From d6a3621650019d553d8c0a4076592f7cbcb71e3f Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 28 Apr 2021 22:50:12 +0530 Subject: [PATCH 05/34] fix: remove scrollable behavior --- frappe/public/js/frappe/dom.js | 2 +- frappe/public/js/frappe/form/grid_row_form.js | 2 +- frappe/public/scss/common/modal.scss | 12 ++++++++---- frappe/public/scss/desk/scrollbar.scss | 18 +++++++++--------- frappe/website/js/bootstrap-4.js | 2 +- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index db9407ed53..2769e9061d 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -319,7 +319,7 @@ frappe.get_data_pill = (label, target_id=null, remove_action=null, image=null) = frappe.get_modal = function(title, content) { return $(`