Merge branch 'develop' into repr_doctype
This commit is contained in:
commit
8d491348a3
26 changed files with 405 additions and 297 deletions
2
.github/helper/semgrep_rules/translate.yml
vendored
2
.github/helper/semgrep_rules/translate.yml
vendored
|
|
@ -42,7 +42,7 @@ rules:
|
|||
|
||||
- id: frappe-translation-python-splitting
|
||||
pattern-either:
|
||||
- pattern: _(...) + ... + _(...)
|
||||
- pattern: _(...) + _(...)
|
||||
- pattern: _("..." + "...")
|
||||
- pattern-regex: '_\([^\)]*\\\s*' # lines broken by `\`
|
||||
- pattern-regex: '_\(\s*\n' # line breaks allowed by python for using ( )
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ be used to build database driven apps.
|
|||
|
||||
Read the documentation: https://frappeframework.com/docs
|
||||
"""
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
from six import iteritems, binary_type, text_type, string_types, PY2
|
||||
from six import iteritems, binary_type, text_type, string_types
|
||||
from werkzeug.local import Local, release_local
|
||||
import os, sys, importlib, inspect, json
|
||||
import os, sys, importlib, inspect, json, warnings
|
||||
import typing
|
||||
from past.builtins import cmp
|
||||
import click
|
||||
|
|
@ -27,19 +26,14 @@ from .utils.lazy_loader import lazy_import
|
|||
# Lazy imports
|
||||
faker = lazy_import('faker')
|
||||
|
||||
|
||||
# Harmless for Python 3
|
||||
# For Python 2 set default encoding to utf-8
|
||||
if PY2:
|
||||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
__version__ = '14.0.0-dev'
|
||||
|
||||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
controllers = {}
|
||||
warnings.simplefilter('always', DeprecationWarning)
|
||||
warnings.simplefilter('always', PendingDeprecationWarning)
|
||||
|
||||
class _dict(dict):
|
||||
"""dict like object that exposes keys as attributes"""
|
||||
|
|
|
|||
|
|
@ -185,7 +185,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 \
|
||||
|
|
@ -296,7 +296,7 @@ def serve(port=8000, profile=False, no_reload=False, no_threading=False, site=No
|
|||
from werkzeug.serving import run_simple
|
||||
patch_werkzeug_reloader()
|
||||
|
||||
if profile:
|
||||
if profile or os.environ.get('USE_PROFILER'):
|
||||
application = ProfilerMiddleware(application, sort_by=('cumtime', 'calls'))
|
||||
|
||||
if not os.environ.get('NO_STATICS'):
|
||||
|
|
|
|||
|
|
@ -254,9 +254,7 @@ def list_apps(context, format):
|
|||
frappe.destroy()
|
||||
|
||||
if format == "json":
|
||||
import json
|
||||
|
||||
click.echo(json.dumps(summary_dict))
|
||||
click.echo(frappe.as_json(summary_dict))
|
||||
|
||||
@click.command('add-system-manager')
|
||||
@click.argument('email')
|
||||
|
|
|
|||
|
|
@ -96,22 +96,54 @@ def destroy_all_sessions(context, reason=None):
|
|||
raise SiteNotSpecifiedError
|
||||
|
||||
@click.command('show-config')
|
||||
@click.option("--format", "-f", type=click.Choice(["text", "json"]), default="text")
|
||||
@pass_context
|
||||
def show_config(context):
|
||||
"print configuration file"
|
||||
print("\t\033[92m{:<50}\033[0m \033[92m{:<15}\033[0m".format('Config','Value'))
|
||||
sites_path = os.path.join(frappe.utils.get_bench_path(), 'sites')
|
||||
site_path = context.sites[0]
|
||||
configuration = frappe.get_site_config(sites_path=sites_path, site_path=site_path)
|
||||
print_config(configuration)
|
||||
def show_config(context, format):
|
||||
"Print configuration file to STDOUT in speified format"
|
||||
|
||||
if not context.sites:
|
||||
raise SiteNotSpecifiedError
|
||||
|
||||
def print_config(config):
|
||||
for conf, value in config.items():
|
||||
if isinstance(value, dict):
|
||||
print_config(value)
|
||||
else:
|
||||
print("\t{:<50} {:<15}".format(conf, value))
|
||||
sites_config = {}
|
||||
sites_path = os.getcwd()
|
||||
|
||||
from frappe.utils.commands import render_table
|
||||
|
||||
def transform_config(config, prefix=None):
|
||||
prefix = f"{prefix}." if prefix else ""
|
||||
site_config = []
|
||||
|
||||
for conf, value in config.items():
|
||||
if isinstance(value, dict):
|
||||
site_config += transform_config(value, prefix=f"{prefix}{conf}")
|
||||
else:
|
||||
log_value = json.dumps(value) if isinstance(value, list) else value
|
||||
site_config += [[f"{prefix}{conf}", log_value]]
|
||||
|
||||
return site_config
|
||||
|
||||
for site in context.sites:
|
||||
frappe.init(site)
|
||||
|
||||
if len(context.sites) != 1 and format == "text":
|
||||
if context.sites.index(site) != 0:
|
||||
click.echo()
|
||||
click.secho(f"Site {site}", fg="yellow")
|
||||
|
||||
configuration = frappe.get_site_config(sites_path=sites_path, site_path=site)
|
||||
|
||||
if format == "text":
|
||||
data = transform_config(configuration)
|
||||
data.insert(0, ['Config','Value'])
|
||||
render_table(data)
|
||||
|
||||
if format == "json":
|
||||
sites_config[site] = configuration
|
||||
|
||||
frappe.destroy()
|
||||
|
||||
if format == "json":
|
||||
click.echo(frappe.as_json(sites_config))
|
||||
|
||||
|
||||
@click.command('reset-perms')
|
||||
|
|
@ -470,6 +502,7 @@ def console(context):
|
|||
locals()[app] = __import__(app)
|
||||
except ModuleNotFoundError:
|
||||
failed_to_import.append(app)
|
||||
all_apps.remove(app)
|
||||
|
||||
print("Apps in this namespace:\n{}".format(", ".join(all_apps)))
|
||||
if failed_to_import:
|
||||
|
|
@ -657,20 +690,27 @@ def make_app(destination, app_name):
|
|||
@click.command('set-config')
|
||||
@click.argument('key')
|
||||
@click.argument('value')
|
||||
@click.option('-g', '--global', 'global_', is_flag = True, default = False, help = 'Set Global Site Config')
|
||||
@click.option('--as-dict', is_flag=True, default=False)
|
||||
@click.option('-g', '--global', 'global_', is_flag=True, default=False, help='Set value in bench config')
|
||||
@click.option('-p', '--parse', is_flag=True, default=False, help='Evaluate as Python Object')
|
||||
@click.option('--as-dict', is_flag=True, default=False, help='Legacy: Evaluate as Python Object')
|
||||
@pass_context
|
||||
def set_config(context, key, value, global_ = False, as_dict=False):
|
||||
def set_config(context, key, value, global_=False, parse=False, as_dict=False):
|
||||
"Insert/Update a value in site_config.json"
|
||||
from frappe.installer import update_site_config
|
||||
import ast
|
||||
|
||||
if as_dict:
|
||||
from frappe.utils.commands import warn
|
||||
warn("--as-dict will be deprecated in v14. Use --parse instead", category=PendingDeprecationWarning)
|
||||
parse = as_dict
|
||||
|
||||
if parse:
|
||||
import ast
|
||||
value = ast.literal_eval(value)
|
||||
|
||||
if global_:
|
||||
sites_path = os.getcwd() # big assumption.
|
||||
sites_path = os.getcwd()
|
||||
common_site_config_path = os.path.join(sites_path, 'common_site_config.json')
|
||||
update_site_config(key, value, validate = False, site_config_path = common_site_config_path)
|
||||
update_site_config(key, value, validate=False, site_config_path=common_site_config_path)
|
||||
else:
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
|
|
@ -727,50 +767,6 @@ def rebuild_global_search(context, static_pages=False):
|
|||
if not context.sites:
|
||||
raise SiteNotSpecifiedError
|
||||
|
||||
@click.command('auto-deploy')
|
||||
@click.argument('app')
|
||||
@click.option('--migrate', is_flag=True, default=False, help='Migrate after pulling')
|
||||
@click.option('--restart', is_flag=True, default=False, help='Restart after migration')
|
||||
@click.option('--remote', default='upstream', help='Remote, default is "upstream"')
|
||||
@pass_context
|
||||
def auto_deploy(context, app, migrate=False, restart=False, remote='upstream'):
|
||||
'''Pull and migrate sites that have new version'''
|
||||
from frappe.utils.gitutils import get_app_branch
|
||||
from frappe.utils import get_sites
|
||||
|
||||
branch = get_app_branch(app)
|
||||
app_path = frappe.get_app_path(app)
|
||||
|
||||
# fetch
|
||||
subprocess.check_output(['git', 'fetch', remote, branch], cwd = app_path)
|
||||
|
||||
# get diff
|
||||
if subprocess.check_output(['git', 'diff', '{0}..{1}/{0}'.format(branch, remote)], cwd = app_path):
|
||||
print('Updates found for {0}'.format(app))
|
||||
if app=='frappe':
|
||||
# run bench update
|
||||
import shlex
|
||||
subprocess.check_output(shlex.split('bench update --no-backup'), cwd = '..')
|
||||
else:
|
||||
updated = False
|
||||
subprocess.check_output(['git', 'pull', '--rebase', remote, branch],
|
||||
cwd = app_path)
|
||||
# find all sites with that app
|
||||
for site in get_sites():
|
||||
frappe.init(site)
|
||||
if app in frappe.get_installed_apps():
|
||||
print('Updating {0}'.format(site))
|
||||
updated = True
|
||||
subprocess.check_output(['bench', '--site', site, 'clear-cache'], cwd = '..')
|
||||
if migrate:
|
||||
subprocess.check_output(['bench', '--site', site, 'migrate'], cwd = '..')
|
||||
frappe.destroy()
|
||||
|
||||
if updated or restart:
|
||||
subprocess.check_output(['bench', 'restart'], cwd = '..')
|
||||
else:
|
||||
print('No Updates')
|
||||
|
||||
|
||||
commands = [
|
||||
build,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -377,10 +377,17 @@ def handle_duration_fieldtype_values(result, columns):
|
|||
|
||||
if fieldtype == "Duration":
|
||||
for entry in range(0, len(result)):
|
||||
val_in_seconds = result[entry][i]
|
||||
if val_in_seconds:
|
||||
duration_val = format_duration(val_in_seconds)
|
||||
result[entry][i] = duration_val
|
||||
row = result[entry]
|
||||
if isinstance(row, dict):
|
||||
val_in_seconds = row[col.fieldname]
|
||||
if val_in_seconds:
|
||||
duration_val = format_duration(val_in_seconds)
|
||||
row[col.fieldname] = duration_val
|
||||
else:
|
||||
val_in_seconds = row[i]
|
||||
if val_in_seconds:
|
||||
duration_val = format_duration(val_in_seconds)
|
||||
row[i] = duration_val
|
||||
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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 "<!-- signature-included -->" 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')
|
||||
return email_account.get('brand_logo')
|
||||
|
|
|
|||
|
|
@ -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("<!--email open check-->", quopri.encodestring('<img src="https://{}/api/method/frappe.core.doctype.communication.email.mark_email_as_seen?name={}"/>'.format(frappe.local.site, email.communication).encode()).decode())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ frappe.get_data_pill = (label, target_id=null, remove_action=null, image=null) =
|
|||
|
||||
frappe.get_modal = function(title, content) {
|
||||
return $(`<div class="modal fade" style="overflow: auto;" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="fill-width flex title-section">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export default class GridRowForm {
|
|||
</div>
|
||||
</div>
|
||||
<div class="grid-form-body">
|
||||
<div class="form-area scrollable"></div>
|
||||
<div class="form-area"></div>
|
||||
<div class="grid-footer-toolbar hidden-xs flex justify-between">
|
||||
<div class="grid-shortcuts">
|
||||
<span> ${frappe.utils.icon("keyboard", "md")} </span>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -2,25 +2,50 @@ h5.modal-title {
|
|||
margin: 0px !important;
|
||||
}
|
||||
|
||||
body.modal-open {
|
||||
overflow: auto;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
// Hack to fix incorrect padding applied by Bootstrap
|
||||
body.modal-open[style^="padding-right"] {
|
||||
padding-right: 12px !important;
|
||||
|
||||
header.navbar {
|
||||
padding-right: 12px !important;
|
||||
margin-right: -12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
// Same scrollbar as body
|
||||
scrollbar-width: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
// Hide scrollbar on touch devices
|
||||
@media(max-width: 991px) {
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
.modal-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
background: inherit;
|
||||
padding: var(--padding-md) var(--padding-lg);
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
// padding-bottom: 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
.modal-title {
|
||||
font-weight: 500;
|
||||
line-height: 2em;
|
||||
font-size: $font-size-lg;
|
||||
max-width: calc(100% - 80px);
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
|
|
@ -60,9 +85,17 @@ body.modal-open {
|
|||
}
|
||||
}
|
||||
|
||||
.awesomplete ul {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
background: inherit;
|
||||
padding: var(--padding-md) var(--padding-lg);
|
||||
border-top: 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
justify-content: space-between;
|
||||
|
||||
button {
|
||||
|
|
|
|||
|
|
@ -442,6 +442,11 @@ kbd {
|
|||
/*rtl styles*/
|
||||
|
||||
.frappe-rtl {
|
||||
text-align: right;
|
||||
.modal-actions {
|
||||
right: auto !important;
|
||||
left: 5px;
|
||||
}
|
||||
input, textarea {
|
||||
direction: rtl !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,6 @@ html {
|
|||
}
|
||||
|
||||
/* Works on Chrome, Edge, and Safari */
|
||||
*::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb-color);
|
||||
}
|
||||
|
|
@ -23,7 +18,12 @@ html {
|
|||
background: var(--scrollbar-track-color);
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar {
|
||||
width: unset;
|
||||
height: unset;
|
||||
*::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ class TestCommands(BaseTestCommands):
|
|||
|
||||
# test 7: take a backup with frappe.conf.backup.includes
|
||||
self.execute(
|
||||
"bench --site {site} set-config backup '{includes}' --as-dict",
|
||||
"bench --site {site} set-config backup '{includes}' --parse",
|
||||
{"includes": json.dumps(backup["includes"])},
|
||||
)
|
||||
self.execute("bench --site {site} backup --verbose")
|
||||
|
|
@ -226,7 +226,7 @@ class TestCommands(BaseTestCommands):
|
|||
|
||||
# test 8: take a backup with frappe.conf.backup.excludes
|
||||
self.execute(
|
||||
"bench --site {site} set-config backup '{excludes}' --as-dict",
|
||||
"bench --site {site} set-config backup '{excludes}' --parse",
|
||||
{"excludes": json.dumps(backup["excludes"])},
|
||||
)
|
||||
self.execute("bench --site {site} backup --verbose")
|
||||
|
|
@ -376,6 +376,32 @@ class TestCommands(BaseTestCommands):
|
|||
self.execute("bench --site {site} list-apps -f json")
|
||||
self.assertIsInstance(json.loads(self.stdout), dict)
|
||||
|
||||
def test_show_config(self):
|
||||
# test 1: sanity check for command
|
||||
self.execute("bench --site all show-config")
|
||||
self.assertEquals(self.returncode, 0)
|
||||
|
||||
# test 2: test keys in table text
|
||||
self.execute(
|
||||
"bench --site {site} set-config test_key '{second_order}' --parse",
|
||||
{"second_order": json.dumps({"test_key": "test_value"})},
|
||||
)
|
||||
self.execute("bench --site {site} show-config")
|
||||
self.assertEquals(self.returncode, 0)
|
||||
self.assertIn("test_key.test_key", self.stdout.split())
|
||||
self.assertIn("test_value", self.stdout.split())
|
||||
|
||||
# test 3: parse json format
|
||||
self.execute("bench --site all show-config --format json")
|
||||
self.assertEquals(self.returncode, 0)
|
||||
self.assertIsInstance(json.loads(self.stdout), dict)
|
||||
|
||||
self.execute("bench --site {site} show-config --format json")
|
||||
self.assertIsInstance(json.loads(self.stdout), dict)
|
||||
|
||||
self.execute("bench --site {site} show-config -f json")
|
||||
self.assertIsInstance(json.loads(self.stdout), dict)
|
||||
|
||||
def test_get_bench_relative_path(self):
|
||||
bench_path = frappe.utils.get_bench_path()
|
||||
test1_path = os.path.join(bench_path, "test1.txt")
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import functools
|
||||
import requests
|
||||
from terminaltables import AsciiTable
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1024)
|
||||
def get_first_party_apps():
|
||||
"""Get list of all apps under orgs: frappe. erpnext from GitHub"""
|
||||
import requests
|
||||
|
||||
apps = []
|
||||
for org in ["frappe", "erpnext"]:
|
||||
req = requests.get(f"https://api.github.com/users/{org}/repos", {"type": "sources", "per_page": 200})
|
||||
|
|
@ -15,6 +14,8 @@ def get_first_party_apps():
|
|||
|
||||
|
||||
def render_table(data):
|
||||
from terminaltables import AsciiTable
|
||||
|
||||
print(AsciiTable(data).table)
|
||||
|
||||
|
||||
|
|
@ -49,3 +50,9 @@ def log(message, colour=''):
|
|||
colour = colours.get(colour, "")
|
||||
end_line = '\033[0m'
|
||||
print(colour + message + end_line)
|
||||
|
||||
|
||||
def warn(message, category=None):
|
||||
from warnings import warn
|
||||
|
||||
warn(message=message, category=category, stacklevel=2)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -163,11 +163,11 @@ class PersonalDataDeletionRequest(Document):
|
|||
|
||||
def redact_full_match_data(self, ref, email):
|
||||
"""Replaces the entire field value by the values set in the anonymization_value_map"""
|
||||
filter_by = ref["filter_by"]
|
||||
filter_by = ref.get("filter_by", "owner")
|
||||
|
||||
docs = frappe.get_all(
|
||||
ref["doctype"],
|
||||
filters={filter_by: ("like", "%" + email + "%")},
|
||||
filters={filter_by: email},
|
||||
fields=["name", filter_by],
|
||||
)
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ class PersonalDataDeletionRequest(Document):
|
|||
return anonymize_fields_dict
|
||||
|
||||
def redact_doc(self, doc, ref):
|
||||
filter_by = ref["filter_by"]
|
||||
filter_by = ref.get("filter_by", "owner")
|
||||
meta = frappe.get_meta(ref["doctype"])
|
||||
filter_by_meta = meta.get_field(filter_by)
|
||||
|
||||
|
|
|
|||
2
frappe/website/js/bootstrap-4.js
vendored
2
frappe/website/js/bootstrap-4.js
vendored
|
|
@ -21,7 +21,7 @@ $('.dropdown-menu a.dropdown-toggle').on('click', function (e) {
|
|||
frappe.get_modal = function (title, content) {
|
||||
return $(
|
||||
`<div class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${title}</h5>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue