fix: ruff fixes
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
This commit is contained in:
parent
fbb0e44791
commit
26ae0f3460
128 changed files with 696 additions and 557 deletions
2
.github/helper/ci.py
vendored
2
.github/helper/ci.py
vendored
|
|
@ -1,8 +1,8 @@
|
||||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# MIT License. See LICENSE
|
# MIT License. See LICENSE
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import json
|
|
||||||
|
|
||||||
STANDARD_INCLUSIONS = ["*.py"]
|
STANDARD_INCLUSIONS = ["*.py"]
|
||||||
|
|
||||||
|
|
|
||||||
15
.github/helper/documentation.py
vendored
15
.github/helper/documentation.py
vendored
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
import requests
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
WEBSITE_REPOS = [
|
WEBSITE_REPOS = [
|
||||||
"erpnext_com",
|
"erpnext_com",
|
||||||
|
|
@ -36,11 +36,7 @@ def is_documentation_link(word: str) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def contains_documentation_link(body: str) -> bool:
|
def contains_documentation_link(body: str) -> bool:
|
||||||
return any(
|
return any(is_documentation_link(word) for line in body.splitlines() for word in line.split())
|
||||||
is_documentation_link(word)
|
|
||||||
for line in body.splitlines()
|
|
||||||
for word in line.split()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def check_pull_request(number: str) -> "tuple[int, str]":
|
def check_pull_request(number: str) -> "tuple[int, str]":
|
||||||
|
|
@ -53,12 +49,7 @@ def check_pull_request(number: str) -> "tuple[int, str]":
|
||||||
head_sha = (payload.get("head") or {}).get("sha")
|
head_sha = (payload.get("head") or {}).get("sha")
|
||||||
body = (payload.get("body") or "").lower()
|
body = (payload.get("body") or "").lower()
|
||||||
|
|
||||||
if (
|
if not title.startswith("feat") or not head_sha or "no-docs" in body or "backport" in body:
|
||||||
not title.startswith("feat")
|
|
||||||
or not head_sha
|
|
||||||
or "no-docs" in body
|
|
||||||
or "backport" in body
|
|
||||||
):
|
|
||||||
return 0, "Skipping documentation checks... 🏃"
|
return 0, "Skipping documentation checks... 🏃"
|
||||||
|
|
||||||
if contains_documentation_link(body):
|
if contains_documentation_link(body):
|
||||||
|
|
|
||||||
8
.github/helper/roulette.py
vendored
8
.github/helper/roulette.py
vendored
|
|
@ -6,11 +6,11 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from functools import lru_cache
|
from functools import cache
|
||||||
from urllib.error import HTTPError
|
from urllib.error import HTTPError
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=None)
|
@cache
|
||||||
def fetch_pr_data(pr_number, repo, endpoint=""):
|
def fetch_pr_data(pr_number, repo, endpoint=""):
|
||||||
api_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
|
api_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
|
||||||
|
|
||||||
|
|
@ -83,9 +83,7 @@ def is_ci(file):
|
||||||
|
|
||||||
|
|
||||||
def is_frontend_code(file):
|
def is_frontend_code(file):
|
||||||
return file.lower().endswith(
|
return file.lower().endswith((".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html"))
|
||||||
(".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_docs(file):
|
def is_docs(file):
|
||||||
|
|
|
||||||
34
.github/helper/translation.py
vendored
34
.github/helper/translation.py
vendored
|
|
@ -2,7 +2,9 @@ import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
errors_encounter = 0
|
errors_encounter = 0
|
||||||
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
|
pattern = re.compile(
|
||||||
|
r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)"
|
||||||
|
)
|
||||||
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
|
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
|
||||||
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
|
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
|
||||||
f_string_pattern = re.compile(r"_\(f[\"']")
|
f_string_pattern = re.compile(r"_\(f[\"']")
|
||||||
|
|
@ -10,44 +12,50 @@ starts_with_f_pattern = re.compile(r"_\(f")
|
||||||
|
|
||||||
# skip first argument
|
# skip first argument
|
||||||
files = sys.argv[1:]
|
files = sys.argv[1:]
|
||||||
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))]
|
files_to_scan = [_file for _file in files if _file.endswith((".py", ".js"))]
|
||||||
|
|
||||||
for _file in files_to_scan:
|
for _file in files_to_scan:
|
||||||
with open(_file, 'r') as f:
|
with open(_file) as f:
|
||||||
print(f'Checking: {_file}')
|
print(f"Checking: {_file}")
|
||||||
file_lines = f.readlines()
|
file_lines = f.readlines()
|
||||||
for line_number, line in enumerate(file_lines, 1):
|
for line_number, line in enumerate(file_lines, 1):
|
||||||
if 'frappe-lint: disable-translate' in line:
|
if "frappe-lint: disable-translate" in line:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if start_matches := start_pattern.search(line):
|
if start_matches := start_pattern.search(line):
|
||||||
if starts_with_f := starts_with_f_pattern.search(line):
|
if starts_with_f := starts_with_f_pattern.search(line):
|
||||||
if has_f_string := f_string_pattern.search(line):
|
if has_f_string := f_string_pattern.search(line):
|
||||||
errors_encounter += 1
|
errors_encounter += 1
|
||||||
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
|
print(
|
||||||
|
f"\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
match = pattern.search(line)
|
match = pattern.search(line)
|
||||||
error_found = False
|
error_found = False
|
||||||
|
|
||||||
if not match and line.endswith((',\n', '[\n')):
|
if not match and line.endswith((",\n", "[\n")):
|
||||||
# concat remaining text to validate multiline pattern
|
# concat remaining text to validate multiline pattern
|
||||||
line = "".join(file_lines[line_number - 1:])
|
line = "".join(file_lines[line_number - 1 :])
|
||||||
line = line[start_matches.start() + 1:]
|
line = line[start_matches.start() + 1 :]
|
||||||
match = pattern.match(line)
|
match = pattern.match(line)
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
error_found = True
|
error_found = True
|
||||||
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}')
|
print(f"\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}")
|
||||||
|
|
||||||
if not error_found and not words_pattern.search(line):
|
if not error_found and not words_pattern.search(line):
|
||||||
error_found = True
|
error_found = True
|
||||||
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}')
|
print(
|
||||||
|
f"\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}"
|
||||||
|
)
|
||||||
|
|
||||||
if error_found:
|
if error_found:
|
||||||
errors_encounter += 1
|
errors_encounter += 1
|
||||||
|
|
||||||
if errors_encounter > 0:
|
if errors_encounter > 0:
|
||||||
print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.')
|
print(
|
||||||
|
'\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.'
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
print('\nGood To Go!')
|
print("\nGood To Go!")
|
||||||
|
|
|
||||||
|
|
@ -152,12 +152,12 @@ class _LazyTranslate:
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
if isinstance(other, (str, _LazyTranslate)):
|
if isinstance(other, str | _LazyTranslate):
|
||||||
return self.value + str(other)
|
return self.value + str(other)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __radd__(self, other):
|
def __radd__(self, other):
|
||||||
if isinstance(other, (str, _LazyTranslate)):
|
if isinstance(other, str | _LazyTranslate):
|
||||||
return str(other) + self.value
|
return str(other) + self.value
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
|
|
@ -1300,7 +1300,7 @@ def get_cached_value(doctype: str, name: str, fieldname: str = "name", as_dict:
|
||||||
|
|
||||||
values = [doc.get(f) for f in fieldname]
|
values = [doc.get(f) for f in fieldname]
|
||||||
if as_dict:
|
if as_dict:
|
||||||
return _dict(zip(fieldname, values))
|
return _dict(zip(fieldname, values, strict=False))
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1644,7 +1644,7 @@ def _load_app_hooks(app_name: str | None = None):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def _is_valid_hook(obj):
|
def _is_valid_hook(obj):
|
||||||
return not isinstance(obj, (types.ModuleType, types.FunctionType, type))
|
return not isinstance(obj, types.ModuleType | types.FunctionType | type)
|
||||||
|
|
||||||
for key, value in inspect.getmembers(app_hooks, predicate=_is_valid_hook):
|
for key, value in inspect.getmembers(app_hooks, predicate=_is_valid_hook):
|
||||||
if not key.startswith("_"):
|
if not key.startswith("_"):
|
||||||
|
|
@ -1904,7 +1904,7 @@ def copy_doc(doc: "Document", ignore_no_copy: bool = True) -> "Document":
|
||||||
if not ignore_no_copy:
|
if not ignore_no_copy:
|
||||||
remove_no_copy_fields(newdoc)
|
remove_no_copy_fields(newdoc)
|
||||||
|
|
||||||
for i, d in enumerate(newdoc.get_all_children()):
|
for d in newdoc.get_all_children():
|
||||||
d.set("__islocal", 1)
|
d.set("__islocal", 1)
|
||||||
|
|
||||||
for fieldname in fields_to_clear:
|
for fieldname in fields_to_clear:
|
||||||
|
|
@ -2493,7 +2493,7 @@ def mock(type, size=1, locale="en"):
|
||||||
if type not in dir(fake):
|
if type not in dir(fake):
|
||||||
raise ValueError("Not a valid mock type.")
|
raise ValueError("Not a valid mock type.")
|
||||||
else:
|
else:
|
||||||
for i in range(size):
|
for _ in range(size):
|
||||||
data = getattr(fake, type)()
|
data = getattr(fake, type)()
|
||||||
results.append(data)
|
results.append(data)
|
||||||
|
|
||||||
|
|
@ -2507,7 +2507,7 @@ def validate_and_sanitize_search_inputs(fn):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
from frappe.desk.search import sanitize_searchfield
|
from frappe.desk.search import sanitize_searchfield
|
||||||
|
|
||||||
kwargs.update(dict(zip(fn.__code__.co_varnames, args)))
|
kwargs.update(dict(zip(fn.__code__.co_varnames, args, strict=False)))
|
||||||
sanitize_searchfield(kwargs["searchfield"])
|
sanitize_searchfield(kwargs["searchfield"])
|
||||||
kwargs["start"] = cint(kwargs["start"])
|
kwargs["start"] = cint(kwargs["start"])
|
||||||
kwargs["page_len"] = cint(kwargs["page_len"])
|
kwargs["page_len"] = cint(kwargs["page_len"])
|
||||||
|
|
@ -2520,7 +2520,7 @@ def validate_and_sanitize_search_inputs(fn):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
from frappe.utils.error import log_error # noqa: backward compatibility
|
from frappe.utils.error import log_error # noqa
|
||||||
|
|
||||||
if _tune_gc:
|
if _tune_gc:
|
||||||
# generational GC gets triggered after certain allocs (g0) which is 700 by default.
|
# generational GC gets triggered after certain allocs (g0) which is 700 by default.
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,7 @@ class CookieManager:
|
||||||
}
|
}
|
||||||
|
|
||||||
def delete_cookie(self, to_delete):
|
def delete_cookie(self, to_delete):
|
||||||
if not isinstance(to_delete, (list, tuple)):
|
if not isinstance(to_delete, list | tuple):
|
||||||
to_delete = [to_delete]
|
to_delete = [to_delete]
|
||||||
|
|
||||||
self.to_delete.extend(to_delete)
|
self.to_delete.extend(to_delete)
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ class TestAutoAssign(FrappeTestCase):
|
||||||
frappe.db.delete("ToDo", {"name": d.name})
|
frappe.db.delete("ToDo", {"name": d.name})
|
||||||
|
|
||||||
# add 5 more assignments
|
# add 5 more assignments
|
||||||
for i in range(5):
|
for _ in range(5):
|
||||||
_make_test_record(public=1)
|
_make_test_record(public=1)
|
||||||
|
|
||||||
# check if each user still has 10 assignments
|
# check if each user still has 10 assignments
|
||||||
|
|
|
||||||
|
|
@ -177,9 +177,6 @@ def symlink(target, link_name, overwrite=False):
|
||||||
if not overwrite:
|
if not overwrite:
|
||||||
return os.symlink(target, link_name)
|
return os.symlink(target, link_name)
|
||||||
|
|
||||||
# os.replace() may fail if files are on different filesystems
|
|
||||||
link_dir = os.path.dirname(link_name)
|
|
||||||
|
|
||||||
# Create link to target with temporary filename
|
# Create link to target with temporary filename
|
||||||
while True:
|
while True:
|
||||||
temp_link_name = f"tmp{frappe.generate_hash()}"
|
temp_link_name = f"tmp{frappe.generate_hash()}"
|
||||||
|
|
|
||||||
|
|
@ -1036,7 +1036,7 @@ def _drop_site(
|
||||||
f"Error: The operation has stopped because backup of {site}'s database failed.",
|
f"Error: The operation has stopped because backup of {site}'s database failed.",
|
||||||
f"Reason: {str(err)}\n",
|
f"Reason: {str(err)}\n",
|
||||||
"Fix the issue and try again.",
|
"Fix the issue and try again.",
|
||||||
"Hint: Use 'bench drop-site {0} --force' to force the removal of {0}".format(site),
|
f"Hint: Use 'bench drop-site {site} --force' to force the removal of {site}",
|
||||||
]
|
]
|
||||||
click.echo("\n".join(messages))
|
click.echo("\n".join(messages))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,7 @@ def new_language(context, lang_code, app):
|
||||||
frappe.connect()
|
frappe.connect()
|
||||||
frappe.translate.write_translations_file(app, lang_code)
|
frappe.translate.write_translations_file(app, lang_code)
|
||||||
|
|
||||||
print(
|
print(f"File created at ./apps/{app}/{app}/translations/{lang_code}.csv")
|
||||||
"File created at ./apps/{app}/{app}/translations/{lang_code}.csv".format(app=app, lang_code=lang_code)
|
|
||||||
)
|
|
||||||
print("You will need to add the language in frappe/geo/languages.json, if you haven't done it already.")
|
print("You will need to add the language in frappe/geo/languages.json, if you haven't done it already.")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -556,7 +556,7 @@ def jupyter(context):
|
||||||
os.mkdir(jupyter_notebooks_path)
|
os.mkdir(jupyter_notebooks_path)
|
||||||
bin_path = os.path.abspath("../env/bin")
|
bin_path = os.path.abspath("../env/bin")
|
||||||
print(
|
print(
|
||||||
"""
|
f"""
|
||||||
Starting Jupyter notebook
|
Starting Jupyter notebook
|
||||||
Run the following in your first cell to connect notebook to frappe
|
Run the following in your first cell to connect notebook to frappe
|
||||||
```
|
```
|
||||||
|
|
@ -566,7 +566,7 @@ frappe.connect()
|
||||||
frappe.local.lang = frappe.db.get_default('lang')
|
frappe.local.lang = frappe.db.get_default('lang')
|
||||||
frappe.db.connect()
|
frappe.db.connect()
|
||||||
```
|
```
|
||||||
""".format(site=site, sites_path=sites_path)
|
"""
|
||||||
)
|
)
|
||||||
os.execv(
|
os.execv(
|
||||||
f"{bin_path}/jupyter",
|
f"{bin_path}/jupyter",
|
||||||
|
|
|
||||||
|
|
@ -125,11 +125,10 @@ def get_preferred_address(doctype, name, preferred_key="is_primary_address"):
|
||||||
FROM
|
FROM
|
||||||
`tabAddress` addr, `tabDynamic Link` dl
|
`tabAddress` addr, `tabDynamic Link` dl
|
||||||
WHERE
|
WHERE
|
||||||
dl.parent = addr.name and dl.link_doctype = %s and
|
dl.parent = addr.name and dl.link_doctype = {} and
|
||||||
dl.link_name = %s and ifnull(addr.disabled, 0) = 0 and
|
dl.link_name = {} and ifnull(addr.disabled, 0) = 0 and
|
||||||
%s = %s
|
{} = {}
|
||||||
"""
|
""".format("%s", "%s", preferred_key, "%s"),
|
||||||
% ("%s", "%s", preferred_key, "%s"),
|
|
||||||
(doctype, name, 1),
|
(doctype, name, 1),
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters):
|
||||||
link_name = filters.pop("link_name")
|
link_name = filters.pop("link_name")
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""select
|
f"""select
|
||||||
`tabContact`.name, `tabContact`.full_name, `tabContact`.company_name
|
`tabContact`.name, `tabContact`.full_name, `tabContact`.company_name
|
||||||
from
|
from
|
||||||
`tabContact`, `tabDynamic Link`
|
`tabContact`, `tabDynamic Link`
|
||||||
|
|
@ -265,12 +265,12 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters):
|
||||||
`tabDynamic Link`.parenttype = 'Contact' and
|
`tabDynamic Link`.parenttype = 'Contact' and
|
||||||
`tabDynamic Link`.link_doctype = %(link_doctype)s and
|
`tabDynamic Link`.link_doctype = %(link_doctype)s and
|
||||||
`tabDynamic Link`.link_name = %(link_name)s and
|
`tabDynamic Link`.link_name = %(link_name)s and
|
||||||
`tabContact`.`{key}` like %(txt)s
|
`tabContact`.`{searchfield}` like %(txt)s
|
||||||
{mcond}
|
{get_match_cond(doctype)}
|
||||||
order by
|
order by
|
||||||
if(locate(%(_txt)s, `tabContact`.full_name), locate(%(_txt)s, `tabContact`.company_name), 99999),
|
if(locate(%(_txt)s, `tabContact`.full_name), locate(%(_txt)s, `tabContact`.company_name), 99999),
|
||||||
`tabContact`.idx desc, `tabContact`.full_name
|
`tabContact`.idx desc, `tabContact`.full_name
|
||||||
limit %(start)s, %(page_len)s """.format(mcond=get_match_cond(doctype), key=searchfield),
|
limit %(start)s, %(page_len)s """,
|
||||||
{
|
{
|
||||||
"txt": "%" + txt + "%",
|
"txt": "%" + txt + "%",
|
||||||
"_txt": txt.replace("%", ""),
|
"_txt": txt.replace("%", ""),
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ def get_columns(filters):
|
||||||
|
|
||||||
|
|
||||||
def get_data(filters):
|
def get_data(filters):
|
||||||
data = []
|
|
||||||
reference_doctype = filters.get("reference_doctype")
|
reference_doctype = filters.get("reference_doctype")
|
||||||
reference_name = filters.get("reference_name")
|
reference_name = filters.get("reference_name")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ class ActivityLog(Document):
|
||||||
|
|
||||||
def set_ip_address(self):
|
def set_ip_address(self):
|
||||||
if self.operation in ("Login", "Logout"):
|
if self.operation in ("Login", "Logout"):
|
||||||
self.ip_address = getattr(frappe.local, "request_ip")
|
self.ip_address = frappe.local.request_ip
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_old_logs(days=None):
|
def clear_old_logs(days=None):
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ def export_json(doctype, path, filters=None, or_filters=None, name=None, order_b
|
||||||
for key in del_keys:
|
for key in del_keys:
|
||||||
if key in doc:
|
if key in doc:
|
||||||
del doc[key]
|
del doc[key]
|
||||||
for k, v in doc.items():
|
for v in doc.values():
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
for child in v:
|
for child in v:
|
||||||
for key in del_keys + ("docstatus", "doctype", "modified", "name"):
|
for key in del_keys + ("docstatus", "doctype", "modified", "name"):
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: MIT. See LICENSE
|
# License: MIT. See LICENSE
|
||||||
|
|
||||||
import io
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
@ -525,7 +524,6 @@ class ImportFile:
|
||||||
# subsequent rows that have blank values in parent columns
|
# subsequent rows that have blank values in parent columns
|
||||||
# are considered as child rows
|
# are considered as child rows
|
||||||
parent_column_indexes = self.header.get_column_indexes(self.doctype)
|
parent_column_indexes = self.header.get_column_indexes(self.doctype)
|
||||||
parent_row_values = first_row.get_values(parent_column_indexes)
|
|
||||||
|
|
||||||
data_without_first_row = data[1:]
|
data_without_first_row = data[1:]
|
||||||
for row in data_without_first_row:
|
for row in data_without_first_row:
|
||||||
|
|
@ -656,7 +654,7 @@ class Row:
|
||||||
for key in frappe.model.default_fields + frappe.model.child_table_fields + ("__islocal",):
|
for key in frappe.model.default_fields + frappe.model.child_table_fields + ("__islocal",):
|
||||||
doc.pop(key, None)
|
doc.pop(key, None)
|
||||||
|
|
||||||
for col, value in zip(columns, values):
|
for col, value in zip(columns, values, strict=False):
|
||||||
df = col.df
|
df = col.df
|
||||||
if value in INVALID_VALUES:
|
if value in INVALID_VALUES:
|
||||||
value = None
|
value = None
|
||||||
|
|
@ -751,7 +749,7 @@ class Row:
|
||||||
|
|
||||||
def parse_value(self, value, col):
|
def parse_value(self, value, col):
|
||||||
df = col.df
|
df = col.df
|
||||||
if isinstance(value, (datetime, date)) and df.fieldtype in ["Date", "Datetime"]:
|
if isinstance(value, datetime | date) and df.fieldtype in ["Date", "Datetime"]:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
value = cstr(value)
|
value = cstr(value)
|
||||||
|
|
@ -774,7 +772,7 @@ class Row:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_date(self, value, column):
|
def get_date(self, value, column):
|
||||||
if isinstance(value, (datetime, date)):
|
if isinstance(value, datetime | date):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
date_format = column.date_format
|
date_format = column.date_format
|
||||||
|
|
@ -938,7 +936,7 @@ class Column:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def guess_date_format(d):
|
def guess_date_format(d):
|
||||||
if isinstance(d, (datetime, date, time)):
|
if isinstance(d, datetime | date | time):
|
||||||
if self.df.fieldtype == "Date":
|
if self.df.fieldtype == "Date":
|
||||||
return "%Y-%m-%d"
|
return "%Y-%m-%d"
|
||||||
if self.df.fieldtype == "Datetime":
|
if self.df.fieldtype == "Datetime":
|
||||||
|
|
@ -1137,7 +1135,6 @@ def build_fields_dict_for_column_matching(parent_doctype):
|
||||||
|
|
||||||
label = (df.label or "").strip()
|
label = (df.label or "").strip()
|
||||||
translated_label = _(label)
|
translated_label = _(label)
|
||||||
parent = df.parent or parent_doctype
|
|
||||||
|
|
||||||
if parent_doctype == doctype:
|
if parent_doctype == doctype:
|
||||||
# for parent doctypes keys will be
|
# for parent doctypes keys will be
|
||||||
|
|
|
||||||
|
|
@ -1363,11 +1363,9 @@ def validate_fields(meta):
|
||||||
|
|
||||||
if not d.get("__islocal") and frappe.db.has_column(d.parent, d.fieldname):
|
if not d.get("__islocal") and frappe.db.has_column(d.parent, d.fieldname):
|
||||||
has_non_unique_values = frappe.db.sql(
|
has_non_unique_values = frappe.db.sql(
|
||||||
"""select `{fieldname}`, count(*)
|
f"""select `{d.fieldname}`, count(*)
|
||||||
from `tab{doctype}` where ifnull(`{fieldname}`, '') != ''
|
from `tab{d.parent}` where ifnull(`{d.fieldname}`, '') != ''
|
||||||
group by `{fieldname}` having count(*) > 1 limit 1""".format(
|
group by `{d.fieldname}` having count(*) > 1 limit 1"""
|
||||||
doctype=d.parent, fieldname=d.fieldname
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if has_non_unique_values and has_non_unique_values[0][0]:
|
if has_non_unique_values and has_non_unique_values[0][0]:
|
||||||
|
|
@ -1541,7 +1539,7 @@ def validate_fields(meta):
|
||||||
field.options = "\n".join(options_list)
|
field.options = "\n".join(options_list)
|
||||||
|
|
||||||
def scrub_fetch_from(field):
|
def scrub_fetch_from(field):
|
||||||
if hasattr(field, "fetch_from") and getattr(field, "fetch_from"):
|
if hasattr(field, "fetch_from") and field.fetch_from:
|
||||||
field.fetch_from = field.fetch_from.strip("\n").strip()
|
field.fetch_from = field.fetch_from.strip("\n").strip()
|
||||||
|
|
||||||
def validate_data_field_type(docfield):
|
def validate_data_field_type(docfield):
|
||||||
|
|
@ -1826,7 +1824,7 @@ def make_module_and_roles(doc, perm_fieldname="permissions"):
|
||||||
r.desk_access = 1
|
r.desk_access = 1
|
||||||
r.flags.ignore_mandatory = r.flags.ignore_permissions = True
|
r.flags.ignore_mandatory = r.flags.ignore_permissions = True
|
||||||
r.insert()
|
r.insert()
|
||||||
except frappe.DoesNotExistError as e:
|
except frappe.DoesNotExistError:
|
||||||
pass
|
pass
|
||||||
except frappe.db.ProgrammingError as e:
|
except frappe.db.ProgrammingError as e:
|
||||||
if frappe.db.is_table_missing(e):
|
if frappe.db.is_table_missing(e):
|
||||||
|
|
|
||||||
|
|
@ -52,15 +52,21 @@ _THROW_EXC = """
|
||||||
frappe.exceptions.ValidationError: what
|
frappe.exceptions.ValidationError: what
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TEST_EXCEPTIONS = {
|
TEST_EXCEPTIONS = (
|
||||||
"erpnext (app)": _RAW_EXC,
|
(
|
||||||
"erpnext (app)": _THROW_EXC,
|
"erpnext (app)",
|
||||||
}
|
_RAW_EXC,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"erpnext (app)",
|
||||||
|
_THROW_EXC,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestExceptionSourceGuessing(FrappeTestCase):
|
class TestExceptionSourceGuessing(FrappeTestCase):
|
||||||
@patch.object(frappe, "get_installed_apps", return_value=["frappe", "erpnext", "3pa"])
|
@patch.object(frappe, "get_installed_apps", return_value=["frappe", "erpnext", "3pa"])
|
||||||
def test_exc_source_guessing(self, _installed_apps):
|
def test_exc_source_guessing(self, _installed_apps):
|
||||||
for source, exc in TEST_EXCEPTIONS.items():
|
for source, exc in TEST_EXCEPTIONS:
|
||||||
result = guess_exception_source(exc)
|
result = guess_exception_source(exc)
|
||||||
self.assertEqual(result, source)
|
self.assertEqual(result, source)
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ class File(Document):
|
||||||
if not self.attached_to_doctype:
|
if not self.attached_to_doctype:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.attached_to_name or not isinstance(self.attached_to_name, (str, int)):
|
if not self.attached_to_name or not isinstance(self.attached_to_name, str | int):
|
||||||
frappe.throw(_("Attached To Name must be a string or an integer"), frappe.ValidationError)
|
frappe.throw(_("Attached To Name must be a string or an integer"), frappe.ValidationError)
|
||||||
|
|
||||||
if self.attached_to_field and SPECIAL_CHAR_PATTERN.search(self.attached_to_field):
|
if self.attached_to_field and SPECIAL_CHAR_PATTERN.search(self.attached_to_field):
|
||||||
|
|
|
||||||
|
|
@ -88,14 +88,13 @@ class Page(Document):
|
||||||
if not os.path.exists(path + ".js"):
|
if not os.path.exists(path + ".js"):
|
||||||
with open(path + ".js", "w") as f:
|
with open(path + ".js", "w") as f:
|
||||||
f.write(
|
f.write(
|
||||||
"""frappe.pages['%s'].on_page_load = function(wrapper) {
|
f"""frappe.pages['{self.name}'].on_page_load = function(wrapper) {{
|
||||||
var page = frappe.ui.make_app_page({
|
var page = frappe.ui.make_app_page({{
|
||||||
parent: wrapper,
|
parent: wrapper,
|
||||||
title: '%s',
|
title: '{self.title}',
|
||||||
single_column: true
|
single_column: true
|
||||||
});
|
}});
|
||||||
}"""
|
}}"""
|
||||||
% (self.name, self.title)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def as_dict(self, no_nulls=False):
|
def as_dict(self, no_nulls=False):
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,7 @@ class Report(Document):
|
||||||
if filters:
|
if filters:
|
||||||
for key, value in filters.items():
|
for key, value in filters.items():
|
||||||
condition, _value = "=", value
|
condition, _value = "=", value
|
||||||
if isinstance(value, (list, tuple)):
|
if isinstance(value, list | tuple):
|
||||||
condition, _value = value
|
condition, _value = value
|
||||||
_filters.append([key, condition, _value])
|
_filters.append([key, condition, _value])
|
||||||
|
|
||||||
|
|
@ -360,7 +360,7 @@ class Report(Document):
|
||||||
def build_data_dict(self, result, columns):
|
def build_data_dict(self, result, columns):
|
||||||
data = []
|
data = []
|
||||||
for row in result:
|
for row in result:
|
||||||
if isinstance(row, (list, tuple)):
|
if isinstance(row, list | tuple):
|
||||||
_row = frappe._dict()
|
_row = frappe._dict()
|
||||||
for i, val in enumerate(row):
|
for i, val in enumerate(row):
|
||||||
_row[columns[i].get("fieldname")] = val
|
_row[columns[i].get("fieldname")] = val
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,12 @@ class TestReport(FrappeTestCase):
|
||||||
self.assertListEqual(["email"], [column.get("fieldname") for column in columns])
|
self.assertListEqual(["email"], [column.get("fieldname") for column in columns])
|
||||||
admin_dict = frappe.core.utils.find(result, lambda d: d["name"] == "Administrator")
|
admin_dict = frappe.core.utils.find(result, lambda d: d["name"] == "Administrator")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
{"name": "Administrator", "user_type": "System User", "email": "admin@example.com"}, admin_dict
|
{
|
||||||
|
"name": "Administrator",
|
||||||
|
"user_type": "System User",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
},
|
||||||
|
admin_dict,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_report_with_custom_column(self):
|
def test_report_with_custom_column(self):
|
||||||
|
|
@ -156,10 +161,18 @@ class TestReport(FrappeTestCase):
|
||||||
)
|
)
|
||||||
result = response.get("result")
|
result = response.get("result")
|
||||||
columns = response.get("columns")
|
columns = response.get("columns")
|
||||||
self.assertListEqual(["name", "email", "user_type"], [column.get("fieldname") for column in columns])
|
self.assertListEqual(
|
||||||
|
["name", "email", "user_type"],
|
||||||
|
[column.get("fieldname") for column in columns],
|
||||||
|
)
|
||||||
admin_dict = frappe.core.utils.find(result, lambda d: d["name"] == "Administrator")
|
admin_dict = frappe.core.utils.find(result, lambda d: d["name"] == "Administrator")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
{"name": "Administrator", "user_type": "System User", "email": "admin@example.com"}, admin_dict
|
{
|
||||||
|
"name": "Administrator",
|
||||||
|
"user_type": "System User",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
},
|
||||||
|
admin_dict,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_report_permissions(self):
|
def test_report_permissions(self):
|
||||||
|
|
@ -167,9 +180,7 @@ class TestReport(FrappeTestCase):
|
||||||
frappe.db.delete("Has Role", {"parent": frappe.session.user, "role": "Test Has Role"})
|
frappe.db.delete("Has Role", {"parent": frappe.session.user, "role": "Test Has Role"})
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
if not frappe.db.exists("Role", "Test Has Role"):
|
if not frappe.db.exists("Role", "Test Has Role"):
|
||||||
role = frappe.get_doc({"doctype": "Role", "role_name": "Test Has Role"}).insert(
|
frappe.get_doc({"doctype": "Role", "role_name": "Test Has Role"}).insert(ignore_permissions=True)
|
||||||
ignore_permissions=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if not frappe.db.exists("Report", "Test Report"):
|
if not frappe.db.exists("Report", "Test Report"):
|
||||||
report = frappe.get_doc(
|
report = frappe.get_doc(
|
||||||
|
|
@ -253,18 +264,18 @@ class TestReport(FrappeTestCase):
|
||||||
report.report_script = """
|
report.report_script = """
|
||||||
totals = {}
|
totals = {}
|
||||||
for user in frappe.get_all('User', fields = ['name', 'user_type', 'creation']):
|
for user in frappe.get_all('User', fields = ['name', 'user_type', 'creation']):
|
||||||
if not user.user_type in totals:
|
if not user.user_type in totals:
|
||||||
totals[user.user_type] = 0
|
totals[user.user_type] = 0
|
||||||
totals[user.user_type] = totals[user.user_type] + 1
|
totals[user.user_type] = totals[user.user_type] + 1
|
||||||
|
|
||||||
data = [
|
data = [
|
||||||
[
|
[
|
||||||
{'fieldname': 'type', 'label': 'Type'},
|
{'fieldname': 'type', 'label': 'Type'},
|
||||||
{'fieldname': 'value', 'label': 'Value'}
|
{'fieldname': 'value', 'label': 'Value'}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{"type":key, "value": value} for key, value in totals.items()
|
{"type":key, "value": value} for key, value in totals.items()
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
report.save()
|
report.save()
|
||||||
|
|
@ -299,13 +310,13 @@ data = [
|
||||||
report.report_script = """
|
report.report_script = """
|
||||||
totals = {}
|
totals = {}
|
||||||
for user in frappe.get_all('User', fields = ['name', 'user_type', 'creation']):
|
for user in frappe.get_all('User', fields = ['name', 'user_type', 'creation']):
|
||||||
if not user.user_type in totals:
|
if not user.user_type in totals:
|
||||||
totals[user.user_type] = 0
|
totals[user.user_type] = 0
|
||||||
totals[user.user_type] = totals[user.user_type] + 1
|
totals[user.user_type] = totals[user.user_type] + 1
|
||||||
|
|
||||||
result = [
|
result = [
|
||||||
{"type":key, "value": value} for key, value in totals.items()
|
{"type":key, "value": value} for key, value in totals.items()
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
report.save()
|
report.save()
|
||||||
|
|
@ -344,15 +355,40 @@ result = [
|
||||||
report_settings = {"tree": True, "parent_field": "parent_value"}
|
report_settings = {"tree": True, "parent_field": "parent_value"}
|
||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
{"fieldname": "parent_column", "label": "Parent Column", "fieldtype": "Data", "width": 10},
|
{
|
||||||
{"fieldname": "column_1", "label": "Column 1", "fieldtype": "Float", "width": 10},
|
"fieldname": "parent_column",
|
||||||
{"fieldname": "column_2", "label": "Column 2", "fieldtype": "Float", "width": 10},
|
"label": "Parent Column",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_1",
|
||||||
|
"label": "Column 1",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_2",
|
||||||
|
"label": "Column 2",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 10,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
result = [
|
result = [
|
||||||
{"parent_column": "Parent 1", "column_1": 200, "column_2": 150.50},
|
{"parent_column": "Parent 1", "column_1": 200, "column_2": 150.50},
|
||||||
{"parent_column": "Child 1", "column_1": 100, "column_2": 75.25, "parent_value": "Parent 1"},
|
{
|
||||||
{"parent_column": "Child 2", "column_1": 100, "column_2": 75.25, "parent_value": "Parent 1"},
|
"parent_column": "Child 1",
|
||||||
|
"column_1": 100,
|
||||||
|
"column_2": 75.25,
|
||||||
|
"parent_value": "Parent 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parent_column": "Child 2",
|
||||||
|
"column_1": 100,
|
||||||
|
"column_2": 75.25,
|
||||||
|
"parent_value": "Parent 1",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
result = add_total_row(
|
result = add_total_row(
|
||||||
|
|
@ -369,13 +405,13 @@ result = [
|
||||||
def test_cte_in_query_report(self):
|
def test_cte_in_query_report(self):
|
||||||
cte_query = textwrap.dedent(
|
cte_query = textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
with enabled_users as (
|
with enabled_users as (
|
||||||
select name
|
select name
|
||||||
from `tabUser`
|
from `tabUser`
|
||||||
where enabled = 1
|
where enabled = 1
|
||||||
)
|
)
|
||||||
select * from enabled_users;
|
select * from enabled_users;
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
report = frappe.get_doc(
|
report = frappe.get_doc(
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ class ServerScript(Document):
|
||||||
if key.startswith("_"):
|
if key.startswith("_"):
|
||||||
continue
|
continue
|
||||||
value = obj[key]
|
value = obj[key]
|
||||||
if isinstance(value, (NamespaceDict, dict)) and value:
|
if isinstance(value, NamespaceDict | dict) and value:
|
||||||
if key == "form_dict":
|
if key == "form_dict":
|
||||||
out.append(["form_dict", 7])
|
out.append(["form_dict", 7])
|
||||||
continue
|
continue
|
||||||
|
|
@ -222,7 +222,7 @@ class ServerScript(Document):
|
||||||
score = 0
|
score = 0
|
||||||
elif isinstance(value, ModuleType):
|
elif isinstance(value, ModuleType):
|
||||||
score = 10
|
score = 10
|
||||||
elif isinstance(value, (FunctionType, MethodType)):
|
elif isinstance(value, FunctionType | MethodType):
|
||||||
score = 9
|
score = 9
|
||||||
elif isinstance(value, type):
|
elif isinstance(value, type):
|
||||||
score = 8
|
score = 8
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ frappe.qb.from_(todo).select(todo.name).where(todo.name == "{todo.name}").run()
|
||||||
name="test_nested_scripts_1",
|
name="test_nested_scripts_1",
|
||||||
script_type="API",
|
script_type="API",
|
||||||
api_method="test_nested_scripts_1",
|
api_method="test_nested_scripts_1",
|
||||||
script=f"""log("nothing")""",
|
script="""log("nothing")""",
|
||||||
)
|
)
|
||||||
script.insert()
|
script.insert()
|
||||||
script.execute_method()
|
script.execute_method()
|
||||||
|
|
@ -228,7 +228,7 @@ frappe.qb.from_(todo).select(todo.name).where(todo.name == "{todo.name}").run()
|
||||||
name="test_nested_scripts_2",
|
name="test_nested_scripts_2",
|
||||||
script_type="API",
|
script_type="API",
|
||||||
api_method="test_nested_scripts_2",
|
api_method="test_nested_scripts_2",
|
||||||
script=f"""frappe.call("test_nested_scripts_1")""",
|
script="""frappe.call("test_nested_scripts_1")""",
|
||||||
)
|
)
|
||||||
script.insert()
|
script.insert()
|
||||||
script.execute_method()
|
script.execute_method()
|
||||||
|
|
|
||||||
|
|
@ -574,10 +574,9 @@ class User(Document):
|
||||||
has_fields = [d.get("name") for d in desc if d.get("name") in ["owner", "modified_by"]]
|
has_fields = [d.get("name") for d in desc if d.get("name") in ["owner", "modified_by"]]
|
||||||
for field in has_fields:
|
for field in has_fields:
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
"""UPDATE `%s`
|
"""UPDATE `{}`
|
||||||
SET `%s` = %s
|
SET `{}` = {}
|
||||||
WHERE `%s` = %s"""
|
WHERE `{}` = {}""".format(tab, field, "%s", field, "%s"),
|
||||||
% (tab, field, "%s", field, "%s"),
|
|
||||||
(new_name, old_name),
|
(new_name, old_name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -620,7 +619,7 @@ class User(Document):
|
||||||
|
|
||||||
def ensure_unique_roles(self):
|
def ensure_unique_roles(self):
|
||||||
exists = []
|
exists = []
|
||||||
for i, d in enumerate(self.get("roles")):
|
for d in self.get("roles"):
|
||||||
if (not d.role) or (d.role in exists):
|
if (not d.role) or (d.role in exists):
|
||||||
self.get("roles").remove(d)
|
self.get("roles").remove(d)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,7 @@ def get_columns_and_fields(doctype):
|
||||||
if df.in_list_view and df.fieldtype in data_fieldtypes:
|
if df.in_list_view and df.fieldtype in data_fieldtypes:
|
||||||
fields.append(f"`{df.fieldname}`")
|
fields.append(f"`{df.fieldname}`")
|
||||||
fieldtype = f"Link/{df.options}" if df.fieldtype == "Link" else df.fieldtype
|
fieldtype = f"Link/{df.options}" if df.fieldtype == "Link" else df.fieldtype
|
||||||
columns.append(
|
columns.append(f"{df.label}:{fieldtype}:{df.width or 100}")
|
||||||
"{label}:{fieldtype}:{width}".format(
|
|
||||||
label=df.label, fieldtype=fieldtype, width=df.width or 100
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return columns, fields
|
return columns, fields
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -592,11 +592,11 @@ class CustomizeForm(Document):
|
||||||
max_length = cint(frappe.db.type_map.get(df.fieldtype)[1])
|
max_length = cint(frappe.db.type_map.get(df.fieldtype)[1])
|
||||||
fieldname = df.fieldname
|
fieldname = df.fieldname
|
||||||
docs = frappe.db.sql(
|
docs = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT name, {fieldname}, LENGTH({fieldname}) AS len
|
SELECT name, {fieldname}, LENGTH({fieldname}) AS len
|
||||||
FROM `tab{doctype}`
|
FROM `tab{self.doc_type}`
|
||||||
WHERE LENGTH({fieldname}) > {max_length}
|
WHERE LENGTH({fieldname}) > {max_length}
|
||||||
""".format(fieldname=fieldname, doctype=self.doc_type, max_length=max_length),
|
""",
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
label = df.label
|
label = df.label
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ def get_data():
|
||||||
|
|
||||||
v = delist(v)
|
v = delist(v)
|
||||||
|
|
||||||
if isinstance(v, (dict, list)):
|
if isinstance(v, dict | list):
|
||||||
try:
|
try:
|
||||||
return frappe.as_json(v)
|
return frappe.as_json(v)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,8 @@ class Database:
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""Connects to a database as set in `site_config.json`."""
|
"""Connects to a database as set in `site_config.json`."""
|
||||||
self._conn: Union["MariadbConnection", "PostgresConnection"] = self.get_connection()
|
self._conn: "MariadbConnection" | "PostgresConnection" = self.get_connection()
|
||||||
self._cursor: Union["MariadbCursor", "PostgresCursor"] = self._conn.cursor()
|
self._cursor: "MariadbCursor" | "PostgresCursor" = self._conn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if execution_timeout := get_query_execution_timeout():
|
if execution_timeout := get_query_execution_timeout():
|
||||||
|
|
@ -183,7 +183,7 @@ class Database:
|
||||||
{"name": "a%", "owner":"test@example.com"})
|
{"name": "a%", "owner":"test@example.com"})
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(query, (MySQLQueryBuilder, PostgreSQLQueryBuilder)):
|
if isinstance(query, MySQLQueryBuilder | PostgreSQLQueryBuilder):
|
||||||
frappe.errprint("Use run method to execute SQL queries generated by Query Engine")
|
frappe.errprint("Use run method to execute SQL queries generated by Query Engine")
|
||||||
|
|
||||||
debug = debug or getattr(self, "debug", False)
|
debug = debug or getattr(self, "debug", False)
|
||||||
|
|
@ -212,7 +212,7 @@ class Database:
|
||||||
|
|
||||||
if values == EmptyQueryValues:
|
if values == EmptyQueryValues:
|
||||||
values = None
|
values = None
|
||||||
elif not isinstance(values, (tuple, dict, list)):
|
elif not isinstance(values, tuple | dict | list):
|
||||||
values = (values,)
|
values = (values,)
|
||||||
|
|
||||||
query, values = self._transform_query(query, values)
|
query, values = self._transform_query(query, values)
|
||||||
|
|
@ -302,7 +302,7 @@ class Database:
|
||||||
elif as_dict:
|
elif as_dict:
|
||||||
keys = [column[0] for column in self._cursor.description]
|
keys = [column[0] for column in self._cursor.description]
|
||||||
for row in result:
|
for row in result:
|
||||||
row = frappe._dict(zip(keys, row))
|
row = frappe._dict(zip(keys, row, strict=False))
|
||||||
if update:
|
if update:
|
||||||
row.update(update)
|
row.update(update)
|
||||||
yield row
|
yield row
|
||||||
|
|
@ -371,7 +371,7 @@ class Database:
|
||||||
return query % {
|
return query % {
|
||||||
k: frappe.db.escape(v) if isinstance(v, str) else v for k, v in values.items()
|
k: frappe.db.escape(v) if isinstance(v, str) else v for k, v in values.items()
|
||||||
}
|
}
|
||||||
elif isinstance(values, (list, tuple)):
|
elif isinstance(values, list | tuple):
|
||||||
return query % tuple(frappe.db.escape(v) if isinstance(v, str) else v for v in values)
|
return query % tuple(frappe.db.escape(v) if isinstance(v, str) else v for v in values)
|
||||||
return query, values
|
return query, values
|
||||||
|
|
||||||
|
|
@ -438,7 +438,7 @@ class Database:
|
||||||
if result:
|
if result:
|
||||||
keys = [column[0] for column in self._cursor.description]
|
keys = [column[0] for column in self._cursor.description]
|
||||||
|
|
||||||
return [frappe._dict(zip(keys, row)) for row in result]
|
return [frappe._dict(zip(keys, row, strict=False)) for row in result]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_db_table_cache(query):
|
def clear_db_table_cache(query):
|
||||||
|
|
|
||||||
|
|
@ -291,18 +291,18 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
||||||
def create_global_search_table(self):
|
def create_global_search_table(self):
|
||||||
if "__global_search" not in self.get_tables():
|
if "__global_search" not in self.get_tables():
|
||||||
self.sql(
|
self.sql(
|
||||||
"""create table __global_search(
|
f"""create table __global_search(
|
||||||
doctype varchar(100),
|
doctype varchar(100),
|
||||||
name varchar({0}),
|
name varchar({self.VARCHAR_LEN}),
|
||||||
title varchar({0}),
|
title varchar({self.VARCHAR_LEN}),
|
||||||
content text,
|
content text,
|
||||||
fulltext(content),
|
fulltext(content),
|
||||||
route varchar({0}),
|
route varchar({self.VARCHAR_LEN}),
|
||||||
published int(1) not null default 0,
|
published int(1) not null default 0,
|
||||||
unique `doctype_name` (doctype, name))
|
unique `doctype_name` (doctype, name))
|
||||||
COLLATE=utf8mb4_unicode_ci
|
COLLATE=utf8mb4_unicode_ci
|
||||||
ENGINE=MyISAM
|
ENGINE=MyISAM
|
||||||
CHARACTER SET=utf8mb4""".format(self.VARCHAR_LEN)
|
CHARACTER SET=utf8mb4"""
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_user_settings_table(self):
|
def create_user_settings_table(self):
|
||||||
|
|
@ -322,7 +322,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
||||||
def get_table_columns_description(self, table_name):
|
def get_table_columns_description(self, table_name):
|
||||||
"""Return list of columns with descriptions."""
|
"""Return list of columns with descriptions."""
|
||||||
return self.sql(
|
return self.sql(
|
||||||
"""select
|
f"""select
|
||||||
column_name as 'name',
|
column_name as 'name',
|
||||||
column_type as 'type',
|
column_type as 'type',
|
||||||
column_default as 'default',
|
column_default as 'default',
|
||||||
|
|
@ -338,7 +338,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
||||||
column_key = 'UNI' as 'unique',
|
column_key = 'UNI' as 'unique',
|
||||||
(is_nullable = 'NO') AS 'not_nullable'
|
(is_nullable = 'NO') AS 'not_nullable'
|
||||||
from information_schema.columns as columns
|
from information_schema.columns as columns
|
||||||
where table_name = '{table_name}' """.format(table_name=table_name),
|
where table_name = '{table_name}' """,
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -359,8 +359,8 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
||||||
|
|
||||||
def has_index(self, table_name, index_name):
|
def has_index(self, table_name, index_name):
|
||||||
return self.sql(
|
return self.sql(
|
||||||
"""SHOW INDEX FROM `{table_name}`
|
f"""SHOW INDEX FROM `{table_name}`
|
||||||
WHERE Key_name='{index_name}'""".format(table_name=table_name, index_name=index_name)
|
WHERE Key_name='{index_name}'"""
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_column_index(self, table_name: str, fieldname: str, unique: bool = False) -> frappe._dict | None:
|
def get_column_index(self, table_name: str, fieldname: str, unique: bool = False) -> frappe._dict | None:
|
||||||
|
|
@ -401,9 +401,8 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
||||||
if not self.has_index(table_name, index_name):
|
if not self.has_index(table_name, index_name):
|
||||||
self.commit()
|
self.commit()
|
||||||
self.sql(
|
self.sql(
|
||||||
"""ALTER TABLE `%s`
|
"""ALTER TABLE `{}`
|
||||||
ADD INDEX `%s`(%s)"""
|
ADD INDEX `{}`({})""".format(table_name, index_name, ", ".join(fields))
|
||||||
% (table_name, index_name, ", ".join(fields))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_unique(self, doctype, fields, constraint_name=None):
|
def add_unique(self, doctype, fields, constraint_name=None):
|
||||||
|
|
@ -419,9 +418,8 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
||||||
):
|
):
|
||||||
self.commit()
|
self.commit()
|
||||||
self.sql(
|
self.sql(
|
||||||
"""alter table `tab%s`
|
"""alter table `tab{}`
|
||||||
add unique `%s`(%s)"""
|
add unique `{}`({})""".format(doctype, constraint_name, ", ".join(fields))
|
||||||
% (doctype, constraint_name, ", ".join(fields))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def updatedb(self, doctype, meta=None):
|
def updatedb(self, doctype, meta=None):
|
||||||
|
|
|
||||||
|
|
@ -113,10 +113,7 @@ def check_database_settings():
|
||||||
result = True
|
result = True
|
||||||
for key, expected_value in REQUIRED_MARIADB_CONFIG.items():
|
for key, expected_value in REQUIRED_MARIADB_CONFIG.items():
|
||||||
if mariadb_variables.get(key) != expected_value:
|
if mariadb_variables.get(key) != expected_value:
|
||||||
print(
|
print(f"For key {key}. Expected value {expected_value}, found value {mariadb_variables.get(key)}")
|
||||||
"For key %s. Expected value %s, found value %s"
|
|
||||||
% (key, expected_value, mariadb_variables.get(key))
|
|
||||||
)
|
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
|
|
|
||||||
|
|
@ -288,14 +288,14 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
|
||||||
def create_global_search_table(self):
|
def create_global_search_table(self):
|
||||||
if "__global_search" not in self.get_tables():
|
if "__global_search" not in self.get_tables():
|
||||||
self.sql(
|
self.sql(
|
||||||
"""create table "__global_search"(
|
f"""create table "__global_search"(
|
||||||
doctype varchar(100),
|
doctype varchar(100),
|
||||||
name varchar({0}),
|
name varchar({self.VARCHAR_LEN}),
|
||||||
title varchar({0}),
|
title varchar({self.VARCHAR_LEN}),
|
||||||
content text,
|
content text,
|
||||||
route varchar({0}),
|
route varchar({self.VARCHAR_LEN}),
|
||||||
published int not null default 0,
|
published int not null default 0,
|
||||||
unique (doctype, name))""".format(self.VARCHAR_LEN)
|
unique (doctype, name))"""
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_user_settings_table(self):
|
def create_user_settings_table(self):
|
||||||
|
|
@ -338,8 +338,8 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
|
||||||
|
|
||||||
def has_index(self, table_name, index_name):
|
def has_index(self, table_name, index_name):
|
||||||
return self.sql(
|
return self.sql(
|
||||||
"""SELECT 1 FROM pg_indexes WHERE tablename='{table_name}'
|
f"""SELECT 1 FROM pg_indexes WHERE tablename='{table_name}'
|
||||||
and indexname='{index_name}' limit 1""".format(table_name=table_name, index_name=index_name)
|
and indexname='{index_name}' limit 1"""
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_index(self, doctype: str, fields: list, index_name: str = None):
|
def add_index(self, doctype: str, fields: list, index_name: str = None):
|
||||||
|
|
@ -368,16 +368,15 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
|
||||||
):
|
):
|
||||||
self.commit()
|
self.commit()
|
||||||
self.sql(
|
self.sql(
|
||||||
"""ALTER TABLE `tab%s`
|
"""ALTER TABLE `tab{}`
|
||||||
ADD CONSTRAINT %s UNIQUE (%s)"""
|
ADD CONSTRAINT {} UNIQUE ({})""".format(doctype, constraint_name, ", ".join(fields))
|
||||||
% (doctype, constraint_name, ", ".join(fields))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_table_columns_description(self, table_name):
|
def get_table_columns_description(self, table_name):
|
||||||
"""Return list of columns with description."""
|
"""Return list of columns with description."""
|
||||||
# pylint: disable=W1401
|
# pylint: disable=W1401
|
||||||
return self.sql(
|
return self.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT a.column_name AS name,
|
SELECT a.column_name AS name,
|
||||||
CASE LOWER(a.data_type)
|
CASE LOWER(a.data_type)
|
||||||
WHEN 'character varying' THEN CONCAT('varchar(', a.character_maximum_length ,')')
|
WHEN 'character varying' THEN CONCAT('varchar(', a.character_maximum_length ,')')
|
||||||
|
|
@ -398,7 +397,7 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
|
||||||
ON SUBSTRING(b.indexdef, '(.*)') LIKE CONCAT('%', a.column_name, '%')
|
ON SUBSTRING(b.indexdef, '(.*)') LIKE CONCAT('%', a.column_name, '%')
|
||||||
WHERE a.table_name = '{table_name}'
|
WHERE a.table_name = '{table_name}'
|
||||||
GROUP BY a.column_name, a.data_type, a.column_default, a.character_maximum_length, a.is_nullable;
|
GROUP BY a.column_name, a.data_type, a.column_default, a.character_maximum_length, a.is_nullable;
|
||||||
""".format(table_name=table_name),
|
""",
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -441,7 +440,7 @@ def modify_query(query):
|
||||||
|
|
||||||
def modify_values(values):
|
def modify_values(values):
|
||||||
def modify_value(value):
|
def modify_value(value):
|
||||||
if isinstance(value, (list, tuple)):
|
if isinstance(value, list | tuple):
|
||||||
value = tuple(modify_values(value))
|
value = tuple(modify_values(value))
|
||||||
|
|
||||||
elif isinstance(value, int):
|
elif isinstance(value, int):
|
||||||
|
|
@ -455,7 +454,7 @@ def modify_values(values):
|
||||||
if isinstance(values, dict):
|
if isinstance(values, dict):
|
||||||
for k, v in values.items():
|
for k, v in values.items():
|
||||||
values[k] = modify_value(v)
|
values[k] = modify_value(v)
|
||||||
elif isinstance(values, (tuple, list)):
|
elif isinstance(values, tuple | list):
|
||||||
new_values = []
|
new_values = []
|
||||||
for val in values:
|
for val in values:
|
||||||
new_values.append(modify_value(val))
|
new_values.append(modify_value(val))
|
||||||
|
|
|
||||||
|
|
@ -54,16 +54,14 @@ class PostgresTable(DBTable):
|
||||||
|
|
||||||
def create_indexes(self):
|
def create_indexes(self):
|
||||||
create_index_query = ""
|
create_index_query = ""
|
||||||
for key, col in self.columns.items():
|
for col in self.columns.values():
|
||||||
if (
|
if (
|
||||||
col.set_index
|
col.set_index
|
||||||
and col.fieldtype in frappe.db.type_map
|
and col.fieldtype in frappe.db.type_map
|
||||||
and frappe.db.type_map.get(col.fieldtype)[0] not in ("text", "longtext")
|
and frappe.db.type_map.get(col.fieldtype)[0] not in ("text", "longtext")
|
||||||
):
|
):
|
||||||
create_index_query += (
|
create_index_query += (
|
||||||
'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format(
|
f'CREATE INDEX IF NOT EXISTS "{col.fieldname}" ON `{self.table_name}`(`{col.fieldname}`);'
|
||||||
index_name=col.fieldname, table_name=self.table_name, field=col.fieldname
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if create_index_query:
|
if create_index_query:
|
||||||
# nosemgrep
|
# nosemgrep
|
||||||
|
|
@ -115,9 +113,7 @@ class PostgresTable(DBTable):
|
||||||
for col in self.add_index:
|
for col in self.add_index:
|
||||||
# if index key not exists
|
# if index key not exists
|
||||||
create_contraint_query += (
|
create_contraint_query += (
|
||||||
'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format(
|
f'CREATE INDEX IF NOT EXISTS "{col.fieldname}" ON `{self.table_name}`(`{col.fieldname}`);'
|
||||||
index_name=col.fieldname, table_name=self.table_name, field=col.fieldname
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for col in self.add_unique:
|
for col in self.add_unique:
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class Engine:
|
||||||
# add fields
|
# add fields
|
||||||
self.fields = self.parse_fields(fields)
|
self.fields = self.parse_fields(fields)
|
||||||
if not self.fields:
|
if not self.fields:
|
||||||
self.fields = [getattr(self.table, "name")]
|
self.fields = [self.table.name]
|
||||||
|
|
||||||
self.query._child_queries = []
|
self.query._child_queries = []
|
||||||
for field in self.fields:
|
for field in self.fields:
|
||||||
|
|
@ -117,7 +117,7 @@ class Engine:
|
||||||
if filters is None:
|
if filters is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(filters, (str, int)):
|
if isinstance(filters, str | int):
|
||||||
filters = {"name": str(filters)}
|
filters = {"name": str(filters)}
|
||||||
|
|
||||||
if isinstance(filters, Criterion):
|
if isinstance(filters, Criterion):
|
||||||
|
|
@ -126,14 +126,14 @@ class Engine:
|
||||||
elif isinstance(filters, dict):
|
elif isinstance(filters, dict):
|
||||||
self.apply_dict_filters(filters)
|
self.apply_dict_filters(filters)
|
||||||
|
|
||||||
elif isinstance(filters, (list, tuple)):
|
elif isinstance(filters, list | tuple):
|
||||||
if all(isinstance(d, (str, int)) for d in filters) and len(filters) > 0:
|
if all(isinstance(d, str | int) for d in filters) and len(filters) > 0:
|
||||||
self.apply_dict_filters({"name": ("in", filters)})
|
self.apply_dict_filters({"name": ("in", filters)})
|
||||||
else:
|
else:
|
||||||
for filter in filters:
|
for filter in filters:
|
||||||
if isinstance(filter, (str, int, Criterion, dict)):
|
if isinstance(filter, str | int | Criterion | dict):
|
||||||
self.apply_filters(filter)
|
self.apply_filters(filter)
|
||||||
elif isinstance(filter, (list, tuple)):
|
elif isinstance(filter, list | tuple):
|
||||||
self.apply_list_filters(filter)
|
self.apply_list_filters(filter)
|
||||||
|
|
||||||
def apply_list_filters(self, filter: list):
|
def apply_list_filters(self, filter: list):
|
||||||
|
|
@ -150,7 +150,7 @@ class Engine:
|
||||||
def apply_dict_filters(self, filters: dict[str, str | int | list]):
|
def apply_dict_filters(self, filters: dict[str, str | int | list]):
|
||||||
for field, value in filters.items():
|
for field, value in filters.items():
|
||||||
operator = "="
|
operator = "="
|
||||||
if isinstance(value, (list, tuple)):
|
if isinstance(value, list | tuple):
|
||||||
operator, value = value
|
operator, value = value
|
||||||
|
|
||||||
self._apply_filter(field, value, operator)
|
self._apply_filter(field, value, operator)
|
||||||
|
|
@ -187,7 +187,7 @@ class Engine:
|
||||||
if isinstance(_value, bool):
|
if isinstance(_value, bool):
|
||||||
_value = int(_value)
|
_value = int(_value)
|
||||||
|
|
||||||
elif not _value and isinstance(_value, (list, tuple)):
|
elif not _value and isinstance(_value, list | tuple):
|
||||||
_value = ("",)
|
_value = ("",)
|
||||||
|
|
||||||
# Nested set
|
# Nested set
|
||||||
|
|
@ -279,7 +279,7 @@ class Engine:
|
||||||
return MARIADB_SPECIFIC_COMMENT.sub("", stripped_field)
|
return MARIADB_SPECIFIC_COMMENT.sub("", stripped_field)
|
||||||
return stripped_field
|
return stripped_field
|
||||||
|
|
||||||
if isinstance(fields, (list, tuple)):
|
if isinstance(fields, list | tuple):
|
||||||
return [_sanitize_field(field) for field in fields]
|
return [_sanitize_field(field) for field in fields]
|
||||||
elif isinstance(fields, str):
|
elif isinstance(fields, str):
|
||||||
return _sanitize_field(fields)
|
return _sanitize_field(fields)
|
||||||
|
|
@ -304,10 +304,10 @@ class Engine:
|
||||||
if not fields:
|
if not fields:
|
||||||
return []
|
return []
|
||||||
fields = self.sanitize_fields(fields)
|
fields = self.sanitize_fields(fields)
|
||||||
if isinstance(fields, (list, tuple, set)) and None in fields and Field not in fields:
|
if isinstance(fields, list | tuple | set) and None in fields and Field not in fields:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if not isinstance(fields, (list, tuple)):
|
if not isinstance(fields, list | tuple):
|
||||||
fields = [fields]
|
fields = [fields]
|
||||||
|
|
||||||
def parse_field(field: str):
|
def parse_field(field: str):
|
||||||
|
|
|
||||||
|
|
@ -144,9 +144,7 @@ class DBTable:
|
||||||
try:
|
try:
|
||||||
# check for truncation
|
# check for truncation
|
||||||
max_length = frappe.db.sql(
|
max_length = frappe.db.sql(
|
||||||
"""SELECT MAX(CHAR_LENGTH(`{fieldname}`)) FROM `tab{doctype}`""".format(
|
f"""SELECT MAX(CHAR_LENGTH(`{col.fieldname}`)) FROM `tab{self.doctype}`"""
|
||||||
fieldname=col.fieldname, doctype=self.doctype
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except frappe.db.InternalError as e:
|
except frappe.db.InternalError as e:
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ def get_user_default(key, user=None):
|
||||||
d = user_defaults.get(key, None)
|
d = user_defaults.get(key, None)
|
||||||
|
|
||||||
if is_a_user_permission_key(key):
|
if is_a_user_permission_key(key):
|
||||||
if d and isinstance(d, (list, tuple)) and len(d) == 1:
|
if d and isinstance(d, list | tuple) and len(d) == 1:
|
||||||
# Use User Permission value when only when it has a single value
|
# Use User Permission value when only when it has a single value
|
||||||
d = d[0]
|
d = d[0]
|
||||||
else:
|
else:
|
||||||
|
|
@ -31,7 +31,7 @@ def get_user_default(key, user=None):
|
||||||
# If no default value is found, use the User Permission value
|
# If no default value is found, use the User Permission value
|
||||||
d = user_permission_default
|
d = user_permission_default
|
||||||
|
|
||||||
value = isinstance(d, (list, tuple)) and d[0] or d
|
value = isinstance(d, list | tuple) and d[0] or d
|
||||||
if not_in_user_permission(key, value, user):
|
if not_in_user_permission(key, value, user):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -61,14 +61,14 @@ def get_user_default_as_list(key, user=None):
|
||||||
d = user_defaults.get(key, None)
|
d = user_defaults.get(key, None)
|
||||||
|
|
||||||
if is_a_user_permission_key(key):
|
if is_a_user_permission_key(key):
|
||||||
if d and isinstance(d, (list, tuple)) and len(d) == 1:
|
if d and isinstance(d, list | tuple) and len(d) == 1:
|
||||||
# Use User Permission value when only when it has a single value
|
# Use User Permission value when only when it has a single value
|
||||||
d = [d[0]]
|
d = [d[0]]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
d = user_defaults.get(frappe.scrub(key), None)
|
d = user_defaults.get(frappe.scrub(key), None)
|
||||||
|
|
||||||
d = list(filter(None, (not isinstance(d, (list, tuple))) and [d] or d))
|
d = list(filter(None, (not isinstance(d, list | tuple)) and [d] or d))
|
||||||
|
|
||||||
# filter default values if not found in user permission
|
# filter default values if not found in user permission
|
||||||
return [value for value in d if not not_in_user_permission(key, value)]
|
return [value for value in d if not not_in_user_permission(key, value)]
|
||||||
|
|
@ -135,7 +135,7 @@ def add_global_default(key, value):
|
||||||
def get_global_default(key):
|
def get_global_default(key):
|
||||||
d = get_defaults().get(key, None)
|
d = get_defaults().get(key, None)
|
||||||
|
|
||||||
value = isinstance(d, (list, tuple)) and d[0] or d
|
value = isinstance(d, list | tuple) and d[0] or d
|
||||||
if not_in_user_permission(key, value):
|
if not_in_user_permission(key, value):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ queue_prefix = "insert_queue_for_"
|
||||||
|
|
||||||
|
|
||||||
def deferred_insert(doctype: str, records: list[Union[dict, "Document"]] | str):
|
def deferred_insert(doctype: str, records: list[Union[dict, "Document"]] | str):
|
||||||
if isinstance(records, (dict, list)):
|
if isinstance(records, dict | list):
|
||||||
_records = json.dumps(records)
|
_records = json.dumps(records)
|
||||||
else:
|
else:
|
||||||
_records = records
|
_records = records
|
||||||
|
|
|
||||||
|
|
@ -559,11 +559,11 @@ def save_new_widget(doc, page, blocks, new_widgets):
|
||||||
json_config = widgets and dumps(widgets, sort_keys=True, indent=4)
|
json_config = widgets and dumps(widgets, sort_keys=True, indent=4)
|
||||||
|
|
||||||
# Error log body
|
# Error log body
|
||||||
log = """
|
log = f"""
|
||||||
page: {}
|
page: {page}
|
||||||
config: {}
|
config: {json_config}
|
||||||
exception: {}
|
exception: {e}
|
||||||
""".format(page, json_config, e)
|
"""
|
||||||
doc.log_error("Could not save customization", log)
|
doc.log_error("Could not save customization", log)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,9 +124,7 @@ def get_non_standard_warning_message(non_standard_docs_map):
|
||||||
def get_html(docs, doctype):
|
def get_html(docs, doctype):
|
||||||
html = f"<p>{frappe.bold(doctype)}</p>"
|
html = f"<p>{frappe.bold(doctype)}</p>"
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
html += '<div><a href="/app/Form/{doctype}/{doc}">{doc}</a></div>'.format(
|
html += f'<div><a href="/app/Form/{doctype}/{doc}">{doc}</a></div>'
|
||||||
doctype=doctype, doc=doc
|
|
||||||
)
|
|
||||||
html += "<br>"
|
html += "<br>"
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ def get_permission_query_conditions(user):
|
||||||
module_condition = """`tabDashboard Chart`.`module` in ({allowed_modules})
|
module_condition = """`tabDashboard Chart`.`module` in ({allowed_modules})
|
||||||
or `tabDashboard Chart`.`module` is NULL""".format(allowed_modules=",".join(allowed_modules))
|
or `tabDashboard Chart`.`module` is NULL""".format(allowed_modules=",".join(allowed_modules))
|
||||||
|
|
||||||
return """
|
return f"""
|
||||||
((`tabDashboard Chart`.`chart_type` in ('Count', 'Sum', 'Average')
|
((`tabDashboard Chart`.`chart_type` in ('Count', 'Sum', 'Average')
|
||||||
and {doctype_condition})
|
and {doctype_condition})
|
||||||
or
|
or
|
||||||
|
|
@ -63,11 +63,7 @@ def get_permission_query_conditions(user):
|
||||||
and {report_condition}))
|
and {report_condition}))
|
||||||
and
|
and
|
||||||
({module_condition})
|
({module_condition})
|
||||||
""".format(
|
"""
|
||||||
doctype_condition=doctype_condition,
|
|
||||||
report_condition=report_condition,
|
|
||||||
module_condition=module_condition,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def has_permission(doc, ptype, user):
|
def has_permission(doc, ptype, user):
|
||||||
|
|
@ -248,9 +244,7 @@ def get_heatmap_chart_config(chart, filters, heatmap_year):
|
||||||
doctype,
|
doctype,
|
||||||
fields=[
|
fields=[
|
||||||
timestamp_field,
|
timestamp_field,
|
||||||
"{aggregate_function}({value_field})".format(
|
f"{aggregate_function}({value_field})",
|
||||||
aggregate_function=aggregate_function, value_field=value_field
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
filters=filters,
|
filters=filters,
|
||||||
group_by=f"date({datefield})",
|
group_by=f"date({datefield})",
|
||||||
|
|
@ -307,7 +301,7 @@ def get_result(data, timegrain, from_date, to_date, chart_type):
|
||||||
result = [[date, 0] for date in dates]
|
result = [[date, 0] for date in dates]
|
||||||
data_index = 0
|
data_index = 0
|
||||||
if data:
|
if data:
|
||||||
for i, d in enumerate(result):
|
for d in result:
|
||||||
count = 0
|
count = 0
|
||||||
while data_index < len(data) and getdate(data[data_index][0]) <= d[0]:
|
while data_index < len(data) and getdate(data[data_index][0]) <= d[0]:
|
||||||
d[1] += data[data_index][1]
|
d[1] += data[data_index][1]
|
||||||
|
|
|
||||||
|
|
@ -221,9 +221,7 @@ def delete_communication(event, reference_doctype, reference_docname):
|
||||||
def get_permission_query_conditions(user):
|
def get_permission_query_conditions(user):
|
||||||
if not user:
|
if not user:
|
||||||
user = frappe.session.user
|
user = frappe.session.user
|
||||||
return """(`tabEvent`.`event_type`='Public' or `tabEvent`.`owner`={user})""".format(
|
return f"""(`tabEvent`.`event_type`='Public' or `tabEvent`.`owner`={frappe.db.escape(user)})"""
|
||||||
user=frappe.db.escape(user),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def has_permission(doc, user):
|
def has_permission(doc, user):
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,7 @@ def get_permission_query_conditions(user):
|
||||||
if user == "Administrator":
|
if user == "Administrator":
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
return """(`tabKanban Board`.private=0 or `tabKanban Board`.owner={user})""".format(
|
return f"""(`tabKanban Board`.private=0 or `tabKanban Board`.owner={frappe.db.escape(user)})"""
|
||||||
user=frappe.db.escape(user)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def has_permission(doc, ptype, user):
|
def has_permission(doc, ptype, user):
|
||||||
|
|
|
||||||
|
|
@ -101,11 +101,11 @@ def get_permission_query_conditions(user=None):
|
||||||
module_condition = """`tabNumber Card`.`module` in ({allowed_modules})
|
module_condition = """`tabNumber Card`.`module` in ({allowed_modules})
|
||||||
or `tabNumber Card`.`module` is NULL""".format(allowed_modules=",".join(allowed_modules))
|
or `tabNumber Card`.`module` is NULL""".format(allowed_modules=",".join(allowed_modules))
|
||||||
|
|
||||||
return """
|
return f"""
|
||||||
{doctype_condition}
|
{doctype_condition}
|
||||||
and
|
and
|
||||||
{module_condition}
|
{module_condition}
|
||||||
""".format(doctype_condition=doctype_condition, module_condition=module_condition)
|
"""
|
||||||
|
|
||||||
|
|
||||||
def has_permission(doc, ptype, user):
|
def has_permission(doc, ptype, user):
|
||||||
|
|
@ -141,11 +141,7 @@ def get_result(doc, filters, to_date=None):
|
||||||
if function == "count":
|
if function == "count":
|
||||||
fields = [f"{function}(*) as result"]
|
fields = [f"{function}(*) as result"]
|
||||||
else:
|
else:
|
||||||
fields = [
|
fields = [f"{function}({doc.aggregate_function_based_on}) as result"]
|
||||||
"{function}({based_on}) as result".format(
|
|
||||||
function=function, based_on=doc.aggregate_function_based_on
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = []
|
filters = []
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ def get_references_across_doctypes(
|
||||||
for k, v in references_by_dlink_fields.items():
|
for k, v in references_by_dlink_fields.items():
|
||||||
references.setdefault(k, []).extend(v)
|
references.setdefault(k, []).extend(v)
|
||||||
|
|
||||||
for doctype, links in references.items():
|
for links in references.values():
|
||||||
for link in links:
|
for link in links:
|
||||||
link["is_child"] = link["doctype"] in all_child_tables
|
link["is_child"] = link["doctype"] in all_child_tables
|
||||||
return references
|
return references
|
||||||
|
|
|
||||||
|
|
@ -286,9 +286,9 @@ def get_communication_data(
|
||||||
conditions = ""
|
conditions = ""
|
||||||
if after:
|
if after:
|
||||||
# find after a particular date
|
# find after a particular date
|
||||||
conditions += """
|
conditions += f"""
|
||||||
AND C.creation > {}
|
AND C.creation > {after}
|
||||||
""".format(after)
|
"""
|
||||||
|
|
||||||
if doctype == "User":
|
if doctype == "User":
|
||||||
conditions += """
|
conditions += """
|
||||||
|
|
@ -296,23 +296,23 @@ def get_communication_data(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# communications linked to reference_doctype
|
# communications linked to reference_doctype
|
||||||
part1 = """
|
part1 = f"""
|
||||||
SELECT {fields}
|
SELECT {fields}
|
||||||
FROM `tabCommunication` as C
|
FROM `tabCommunication` as C
|
||||||
WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message')
|
WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message')
|
||||||
AND (C.reference_doctype = %(doctype)s AND C.reference_name = %(name)s)
|
AND (C.reference_doctype = %(doctype)s AND C.reference_name = %(name)s)
|
||||||
{conditions}
|
{conditions}
|
||||||
""".format(fields=fields, conditions=conditions)
|
"""
|
||||||
|
|
||||||
# communications linked in Timeline Links
|
# communications linked in Timeline Links
|
||||||
part2 = """
|
part2 = f"""
|
||||||
SELECT {fields}
|
SELECT {fields}
|
||||||
FROM `tabCommunication` as C
|
FROM `tabCommunication` as C
|
||||||
INNER JOIN `tabCommunication Link` ON C.name=`tabCommunication Link`.parent
|
INNER JOIN `tabCommunication Link` ON C.name=`tabCommunication Link`.parent
|
||||||
WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message')
|
WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message')
|
||||||
AND `tabCommunication Link`.link_doctype = %(doctype)s AND `tabCommunication Link`.link_name = %(name)s
|
AND `tabCommunication Link`.link_doctype = %(doctype)s AND `tabCommunication Link`.link_name = %(name)s
|
||||||
{conditions}
|
{conditions}
|
||||||
""".format(fields=fields, conditions=conditions)
|
"""
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,4 @@ def get_next(doctype, value, prev, filters=None, sort_order="desc", sort_field="
|
||||||
|
|
||||||
|
|
||||||
def get_pdf_link(doctype, docname, print_format="Standard", no_letterhead=0):
|
def get_pdf_link(doctype, docname, print_format="Standard", no_letterhead=0):
|
||||||
return "/api/method/frappe.utils.print_format.download_pdf?doctype={doctype}&name={docname}&format={print_format}&no_letterhead={no_letterhead}".format(
|
return f"/api/method/frappe.utils.print_format.download_pdf?doctype={doctype}&name={docname}&format={print_format}&no_letterhead={no_letterhead}"
|
||||||
doctype=doctype, docname=docname, print_format=print_format, no_letterhead=no_letterhead
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,6 @@ def get_energy_point_leaderboard(date_range, company=None, field=None, limit=Non
|
||||||
for user in energy_point_users:
|
for user in energy_point_users:
|
||||||
user_id = user["name"]
|
user_id = user["name"]
|
||||||
user["name"] = get_fullname(user["name"])
|
user["name"] = get_fullname(user["name"])
|
||||||
user["formatted_name"] = '<a href="/app/user-profile/{}">{}</a>'.format(
|
user["formatted_name"] = f'<a href="/app/user-profile/{user_id}">{get_fullname(user_id)}</a>'
|
||||||
user_id, get_fullname(user_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
return energy_point_users
|
return energy_point_users
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ def get_open_count(doctype, name, items=None):
|
||||||
try:
|
try:
|
||||||
external_links_data_for_d = get_external_links(d, name, links)
|
external_links_data_for_d = get_external_links(d, name, links)
|
||||||
out["external_links_found"].append(external_links_data_for_d)
|
out["external_links_found"].append(external_links_data_for_d)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
out["external_links_found"].append({"doctype": d, "open_count": 0, "count": 0})
|
out["external_links_found"].append({"doctype": d, "open_count": 0, "count": 0})
|
||||||
else:
|
else:
|
||||||
external_links_data_for_d = get_external_links(d, name, links)
|
external_links_data_for_d = get_external_links(d, name, links)
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ def normalize_result(result, columns):
|
||||||
# Convert to list of dicts from list of lists/tuples
|
# Convert to list of dicts from list of lists/tuples
|
||||||
data = []
|
data = []
|
||||||
column_names = [column["fieldname"] for column in columns]
|
column_names = [column["fieldname"] for column in columns]
|
||||||
if result and isinstance(result[0], (list, tuple)):
|
if result and isinstance(result[0], list | tuple):
|
||||||
for row in result:
|
for row in result:
|
||||||
row_obj = {}
|
row_obj = {}
|
||||||
for idx, column_name in enumerate(column_names):
|
for idx, column_name in enumerate(column_names):
|
||||||
|
|
@ -507,7 +507,7 @@ def get_data_for_custom_field(doctype, field, names=None):
|
||||||
|
|
||||||
filters = {}
|
filters = {}
|
||||||
if names:
|
if names:
|
||||||
if isinstance(names, (str, bytearray)):
|
if isinstance(names, str | bytearray):
|
||||||
names = frappe.json.loads(names)
|
names = frappe.json.loads(names)
|
||||||
filters.update({"name": ["in", names]})
|
filters.update({"name": ["in", names]})
|
||||||
|
|
||||||
|
|
@ -659,7 +659,7 @@ def has_match(
|
||||||
cell_value = None
|
cell_value = None
|
||||||
if isinstance(row, dict):
|
if isinstance(row, dict):
|
||||||
cell_value = row.get(idx)
|
cell_value = row.get(idx)
|
||||||
elif isinstance(row, (list, tuple)):
|
elif isinstance(row, list | tuple):
|
||||||
cell_value = row[idx]
|
cell_value = row[idx]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
@ -691,10 +691,10 @@ def get_linked_doctypes(columns, data):
|
||||||
|
|
||||||
columns_dict = get_columns_dict(columns)
|
columns_dict = get_columns_dict(columns)
|
||||||
|
|
||||||
for idx, col in enumerate(columns):
|
for idx in range(len(columns)):
|
||||||
df = columns_dict[idx]
|
df = columns_dict[idx]
|
||||||
if df.get("fieldtype") == "Link":
|
if df.get("fieldtype") == "Link":
|
||||||
if data and isinstance(data[0], (list, tuple)):
|
if data and isinstance(data[0], list | tuple):
|
||||||
linked_doctypes[df["options"]] = idx
|
linked_doctypes[df["options"]] = idx
|
||||||
else:
|
else:
|
||||||
# dict
|
# dict
|
||||||
|
|
@ -705,7 +705,7 @@ def get_linked_doctypes(columns, data):
|
||||||
for row in data:
|
for row in data:
|
||||||
if row:
|
if row:
|
||||||
if len(row) != len(columns_with_value):
|
if len(row) != len(columns_with_value):
|
||||||
if isinstance(row, (list, tuple)):
|
if isinstance(row, list | tuple):
|
||||||
row = enumerate(row)
|
row = enumerate(row)
|
||||||
elif isinstance(row, dict):
|
elif isinstance(row, dict):
|
||||||
row = row.items()
|
row = row.items()
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ def get_meta_and_docfield(fieldname, data):
|
||||||
|
|
||||||
def update_wildcard_field_param(data):
|
def update_wildcard_field_param(data):
|
||||||
if (isinstance(data.fields, str) and data.fields == "*") or (
|
if (isinstance(data.fields, str) and data.fields == "*") or (
|
||||||
isinstance(data.fields, (list, tuple)) and len(data.fields) == 1 and data.fields[0] == "*"
|
isinstance(data.fields, list | tuple) and len(data.fields) == 1 and data.fields[0] == "*"
|
||||||
):
|
):
|
||||||
data.fields = get_permitted_fields(data.doctype, parenttype=data.parenttype)
|
data.fields = get_permitted_fields(data.doctype, parenttype=data.parenttype)
|
||||||
return True
|
return True
|
||||||
|
|
@ -390,10 +390,10 @@ def append_totals_row(data):
|
||||||
|
|
||||||
for row in data:
|
for row in data:
|
||||||
for i in range(len(row)):
|
for i in range(len(row)):
|
||||||
if isinstance(row[i], (float, int)):
|
if isinstance(row[i], float | int):
|
||||||
totals[i] = (totals[i] or 0) + row[i]
|
totals[i] = (totals[i] or 0) + row[i]
|
||||||
|
|
||||||
if not isinstance(totals[0], (int, float)):
|
if not isinstance(totals[0], int | float):
|
||||||
totals[0] = "Total"
|
totals[0] = "Total"
|
||||||
|
|
||||||
data.append(totals)
|
data.append(totals)
|
||||||
|
|
@ -568,7 +568,7 @@ def get_stats(stats, doctype, filters=None):
|
||||||
|
|
||||||
except frappe.db.SQLError:
|
except frappe.db.SQLError:
|
||||||
pass
|
pass
|
||||||
except frappe.db.InternalError as e:
|
except frappe.db.InternalError:
|
||||||
# raised when _user_tags column is added on the fly
|
# raised when _user_tags column is added on the fly
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -673,7 +673,7 @@ def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with
|
||||||
for f in filters:
|
for f in filters:
|
||||||
if isinstance(f[1], str) and f[1][0] == "!":
|
if isinstance(f[1], str) and f[1][0] == "!":
|
||||||
flt.append([doctype, f[0], "!=", f[1][1:]])
|
flt.append([doctype, f[0], "!=", f[1][1:]])
|
||||||
elif isinstance(f[1], (list, tuple)) and f[1][0].lower() in (
|
elif isinstance(f[1], list | tuple) and f[1][0].lower() in (
|
||||||
"=",
|
"=",
|
||||||
">",
|
">",
|
||||||
"<",
|
"<",
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ def send_daily():
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
auto_email_report.send()
|
auto_email_report.send()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
auto_email_report.log_error(f"Failed to send {auto_email_report.name} Auto Email Report")
|
auto_email_report.log_error(f"Failed to send {auto_email_report.name} Auto Email Report")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
import email.utils
|
import email.utils
|
||||||
import functools
|
import functools
|
||||||
import imaplib
|
import imaplib
|
||||||
import socket
|
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from poplib import error_proto
|
from poplib import error_proto
|
||||||
|
|
@ -96,7 +95,15 @@ class EmailAccount(Document):
|
||||||
password: DF.Password | None
|
password: DF.Password | None
|
||||||
send_notification_to: DF.SmallText | None
|
send_notification_to: DF.SmallText | None
|
||||||
send_unsubscribe_message: DF.Check
|
send_unsubscribe_message: DF.Check
|
||||||
service: DF.Literal["", "GMail", "Sendgrid", "SparkPost", "Yahoo Mail", "Outlook.com", "Yandex.Mail"]
|
service: DF.Literal[
|
||||||
|
"",
|
||||||
|
"GMail",
|
||||||
|
"Sendgrid",
|
||||||
|
"SparkPost",
|
||||||
|
"Yahoo Mail",
|
||||||
|
"Outlook.com",
|
||||||
|
"Yandex.Mail",
|
||||||
|
]
|
||||||
signature: DF.TextEditor | None
|
signature: DF.TextEditor | None
|
||||||
smtp_port: DF.Data | None
|
smtp_port: DF.Data | None
|
||||||
smtp_server: DF.Data | None
|
smtp_server: DF.Data | None
|
||||||
|
|
@ -191,20 +198,27 @@ class EmailAccount(Document):
|
||||||
self.default_incoming = False
|
self.default_incoming = False
|
||||||
messages.append(
|
messages.append(
|
||||||
_("{} has been disabled. It can only be enabled if {} is checked.").format(
|
_("{} has been disabled. It can only be enabled if {} is checked.").format(
|
||||||
frappe.bold(_("Default Incoming")), frappe.bold(_("Enable Incoming"))
|
frappe.bold(_("Default Incoming")),
|
||||||
|
frappe.bold(_("Enable Incoming")),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if not self.enable_outgoing and self.default_outgoing:
|
if not self.enable_outgoing and self.default_outgoing:
|
||||||
self.default_outgoing = False
|
self.default_outgoing = False
|
||||||
messages.append(
|
messages.append(
|
||||||
_("{} has been disabled. It can only be enabled if {} is checked.").format(
|
_("{} has been disabled. It can only be enabled if {} is checked.").format(
|
||||||
frappe.bold(_("Default Outgoing")), frappe.bold(_("Enable Outgoing"))
|
frappe.bold(_("Default Outgoing")),
|
||||||
|
frappe.bold(_("Enable Outgoing")),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if messages:
|
if messages:
|
||||||
if len(messages) == 1:
|
if len(messages) == 1:
|
||||||
(as_list, messages) = (0, messages[0])
|
(as_list, messages) = (0, messages[0])
|
||||||
frappe.msgprint(messages, as_list=as_list, indicator="orange", title=_("Defaults Updated"))
|
frappe.msgprint(
|
||||||
|
messages,
|
||||||
|
as_list=as_list,
|
||||||
|
indicator="orange",
|
||||||
|
title=_("Defaults Updated"),
|
||||||
|
)
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
"""Check there is only one default of each type."""
|
"""Check there is only one default of each type."""
|
||||||
|
|
@ -284,7 +298,11 @@ class EmailAccount(Document):
|
||||||
"loginfailed",
|
"loginfailed",
|
||||||
]
|
]
|
||||||
|
|
||||||
other_error_codes = ["err[auth]", "errtemporaryerror", "loginviayourwebbrowser"]
|
other_error_codes = [
|
||||||
|
"err[auth]",
|
||||||
|
"errtemporaryerror",
|
||||||
|
"loginviayourwebbrowser",
|
||||||
|
]
|
||||||
|
|
||||||
all_error_codes = auth_error_codes + other_error_codes
|
all_error_codes = auth_error_codes + other_error_codes
|
||||||
|
|
||||||
|
|
@ -563,7 +581,15 @@ class EmailAccount(Document):
|
||||||
seen_status = messages.get("seen_status", {}).get(uid)
|
seen_status = messages.get("seen_status", {}).get(uid)
|
||||||
if self.email_sync_option != "UNSEEN" or seen_status != "SEEN":
|
if self.email_sync_option != "UNSEEN" or seen_status != "SEEN":
|
||||||
# only append the emails with status != 'SEEN' if sync option is set to 'UNSEEN'
|
# only append the emails with status != 'SEEN' if sync option is set to 'UNSEEN'
|
||||||
mails.append(InboundMail(message, self, frappe.safe_decode(uid), seen_status, append_to))
|
mails.append(
|
||||||
|
InboundMail(
|
||||||
|
message,
|
||||||
|
self,
|
||||||
|
frappe.safe_decode(uid),
|
||||||
|
seen_status,
|
||||||
|
append_to,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if not self.enable_incoming:
|
if not self.enable_incoming:
|
||||||
return []
|
return []
|
||||||
|
|
@ -618,7 +644,9 @@ class EmailAccount(Document):
|
||||||
|
|
||||||
def send_auto_reply(self, communication, email):
|
def send_auto_reply(self, communication, email):
|
||||||
"""Send auto reply if set."""
|
"""Send auto reply if set."""
|
||||||
from frappe.core.doctype.communication.email import set_incoming_outgoing_accounts
|
from frappe.core.doctype.communication.email import (
|
||||||
|
set_incoming_outgoing_accounts,
|
||||||
|
)
|
||||||
|
|
||||||
if self.enable_auto_reply:
|
if self.enable_auto_reply:
|
||||||
set_incoming_outgoing_accounts(communication)
|
set_incoming_outgoing_accounts(communication)
|
||||||
|
|
@ -671,7 +699,10 @@ class EmailAccount(Document):
|
||||||
if not self.enable_incoming:
|
if not self.enable_incoming:
|
||||||
frappe.throw(_("Automatic Linking can be activated only if Incoming is enabled."))
|
frappe.throw(_("Automatic Linking can be activated only if Incoming is enabled."))
|
||||||
|
|
||||||
if frappe.db.exists("Email Account", {"enable_automatic_linking": 1, "name": ("!=", self.name)}):
|
if frappe.db.exists(
|
||||||
|
"Email Account",
|
||||||
|
{"enable_automatic_linking": 1, "name": ("!=", self.name)},
|
||||||
|
):
|
||||||
frappe.throw(_("Automatic Linking can be activated only for one Email Account."))
|
frappe.throw(_("Automatic Linking can be activated only for one Email Account."))
|
||||||
|
|
||||||
def append_email_to_sent_folder(self, message):
|
def append_email_to_sent_folder(self, message):
|
||||||
|
|
@ -717,7 +748,9 @@ def notify_unreplied():
|
||||||
"""Sends email notifications if there are unreplied Communications
|
"""Sends email notifications if there are unreplied Communications
|
||||||
and `notify_if_unreplied` is set as true."""
|
and `notify_if_unreplied` is set as true."""
|
||||||
for email_account in frappe.get_all(
|
for email_account in frappe.get_all(
|
||||||
"Email Account", "name", filters={"enable_incoming": 1, "notify_if_unreplied": 1}
|
"Email Account",
|
||||||
|
"name",
|
||||||
|
filters={"enable_incoming": 1, "notify_if_unreplied": 1},
|
||||||
):
|
):
|
||||||
email_account = frappe.get_doc("Email Account", email_account.name)
|
email_account = frappe.get_doc("Email Account", email_account.name)
|
||||||
|
|
||||||
|
|
@ -774,7 +807,12 @@ def pull(now=False):
|
||||||
doctype = frappe.qb.DocType("Email Account")
|
doctype = frappe.qb.DocType("Email Account")
|
||||||
email_accounts = (
|
email_accounts = (
|
||||||
frappe.qb.from_(doctype)
|
frappe.qb.from_(doctype)
|
||||||
.select(doctype.name, doctype.auth_method, doctype.connected_app, doctype.connected_user)
|
.select(
|
||||||
|
doctype.name,
|
||||||
|
doctype.auth_method,
|
||||||
|
doctype.connected_app,
|
||||||
|
doctype.connected_user,
|
||||||
|
)
|
||||||
.where(doctype.enable_incoming == 1)
|
.where(doctype.enable_incoming == 1)
|
||||||
.where(doctype.awaiting_password == 0)
|
.where(doctype.awaiting_password == 0)
|
||||||
.run(as_dict=1)
|
.run(as_dict=1)
|
||||||
|
|
@ -820,10 +858,9 @@ def pull_from_email_account(email_account):
|
||||||
|
|
||||||
|
|
||||||
def get_max_email_uid(email_account):
|
def get_max_email_uid(email_account):
|
||||||
# get maximum uid of emails
|
"""get maximum uid of emails"""
|
||||||
max_uid = 1
|
|
||||||
|
|
||||||
result = frappe.get_all(
|
if result := frappe.get_all(
|
||||||
"Communication",
|
"Communication",
|
||||||
filters={
|
filters={
|
||||||
"communication_medium": "Email",
|
"communication_medium": "Email",
|
||||||
|
|
@ -831,12 +868,9 @@ def get_max_email_uid(email_account):
|
||||||
"email_account": email_account,
|
"email_account": email_account,
|
||||||
},
|
},
|
||||||
fields=["max(uid) as uid"],
|
fields=["max(uid) as uid"],
|
||||||
)
|
):
|
||||||
|
|
||||||
if not result:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return cint(result[0].get("uid", 0)) + 1
|
return cint(result[0].get("uid", 0)) + 1
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def setup_user_email_inbox(email_account, awaiting_password, email_id, enable_outgoing, used_oauth):
|
def setup_user_email_inbox(email_account, awaiting_password, email_id, enable_outgoing, used_oauth):
|
||||||
|
|
@ -868,7 +902,11 @@ def setup_user_email_inbox(email_account, awaiting_password, email_id, enable_ou
|
||||||
|
|
||||||
# check if inbox is alreay configured
|
# check if inbox is alreay configured
|
||||||
user_inbox = (
|
user_inbox = (
|
||||||
frappe.db.get_value("User Email", {"email_account": email_account, "parent": user_name}, ["name"])
|
frappe.db.get_value(
|
||||||
|
"User Email",
|
||||||
|
{"email_account": email_account, "parent": user_name},
|
||||||
|
["name"],
|
||||||
|
)
|
||||||
or None
|
or None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -895,7 +933,11 @@ def remove_user_email_inbox(email_account):
|
||||||
if not email_account:
|
if not email_account:
|
||||||
return
|
return
|
||||||
|
|
||||||
users = frappe.get_all("User Email", filters={"email_account": email_account}, fields=["parent as name"])
|
users = frappe.get_all(
|
||||||
|
"User Email",
|
||||||
|
filters={"email_account": email_account},
|
||||||
|
fields=["parent as name"],
|
||||||
|
)
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
doc = frappe.get_doc("User", user.get("name"))
|
doc = frappe.get_doc("User", user.get("name"))
|
||||||
|
|
|
||||||
|
|
@ -413,10 +413,13 @@ class TestEmailAccount(FrappeTestCase):
|
||||||
@patch("frappe.email.receive.EmailServer.select_imap_folder", return_value=True)
|
@patch("frappe.email.receive.EmailServer.select_imap_folder", return_value=True)
|
||||||
@patch("frappe.email.receive.EmailServer.logout", side_effect=lambda: None)
|
@patch("frappe.email.receive.EmailServer.logout", side_effect=lambda: None)
|
||||||
def mocked_get_inbound_mails(
|
def mocked_get_inbound_mails(
|
||||||
email_account, messages={}, mocked_logout=None, mocked_select_imap_folder=None
|
email_account, messages=None, mocked_logout=None, mocked_select_imap_folder=None
|
||||||
):
|
):
|
||||||
from frappe.email.receive import EmailServer
|
from frappe.email.receive import EmailServer
|
||||||
|
|
||||||
|
if messages is None:
|
||||||
|
messages = {}
|
||||||
|
|
||||||
def get_mocked_messages(**kwargs):
|
def get_mocked_messages(**kwargs):
|
||||||
return messages.get(kwargs["folder"], {})
|
return messages.get(kwargs["folder"], {})
|
||||||
|
|
||||||
|
|
@ -427,7 +430,12 @@ class TestEmailAccount(FrappeTestCase):
|
||||||
|
|
||||||
@patch("frappe.email.receive.EmailServer.select_imap_folder", return_value=True)
|
@patch("frappe.email.receive.EmailServer.select_imap_folder", return_value=True)
|
||||||
@patch("frappe.email.receive.EmailServer.logout", side_effect=lambda: None)
|
@patch("frappe.email.receive.EmailServer.logout", side_effect=lambda: None)
|
||||||
def mocked_email_receive(email_account, messages={}, mocked_logout=None, mocked_select_imap_folder=None):
|
def mocked_email_receive(
|
||||||
|
email_account, messages=None, mocked_logout=None, mocked_select_imap_folder=None
|
||||||
|
):
|
||||||
|
if messages is None:
|
||||||
|
messages = {}
|
||||||
|
|
||||||
def get_mocked_messages(**kwargs):
|
def get_mocked_messages(**kwargs):
|
||||||
return messages.get(kwargs["folder"], {})
|
return messages.get(kwargs["folder"], {})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ def import_from(name, doctype):
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_subscribers(name, email_list):
|
def add_subscribers(name, email_list):
|
||||||
if not isinstance(email_list, (list, tuple)):
|
if not isinstance(email_list, list | tuple):
|
||||||
email_list = email_list.replace(",", "\n").split("\n")
|
email_list = email_list.replace(",", "\n").split("\n")
|
||||||
|
|
||||||
template = frappe.db.get_value("Email Group", name, "welcome_email_template")
|
template = frappe.db.get_value("Email Group", name, "welcome_email_template")
|
||||||
|
|
|
||||||
|
|
@ -356,7 +356,7 @@ class FrappeClient:
|
||||||
def preprocess(self, params):
|
def preprocess(self, params):
|
||||||
"""convert dicts, lists to json"""
|
"""convert dicts, lists to json"""
|
||||||
for key, value in params.items():
|
for key, value in params.items():
|
||||||
if isinstance(value, (dict, list)):
|
if isinstance(value, dict | list):
|
||||||
params[key] = json.dumps(value)
|
params[key] = json.dumps(value)
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ def extract(fileobj, *args, **kwargs):
|
||||||
module = get_module(fileobj.name)
|
module = get_module(fileobj.name)
|
||||||
|
|
||||||
if hasattr(module, "standard_navbar_items"):
|
if hasattr(module, "standard_navbar_items"):
|
||||||
standard_navbar_items = getattr(module, "standard_navbar_items")
|
standard_navbar_items = module.standard_navbar_items
|
||||||
for nav_item in standard_navbar_items:
|
for nav_item in standard_navbar_items:
|
||||||
if label := nav_item.get("item_label"):
|
if label := nav_item.get("item_label"):
|
||||||
item_type = nav_item.get("item_type")
|
item_type = nav_item.get("item_type")
|
||||||
|
|
@ -29,7 +29,7 @@ def extract(fileobj, *args, **kwargs):
|
||||||
)
|
)
|
||||||
|
|
||||||
if hasattr(module, "standard_help_items"):
|
if hasattr(module, "standard_help_items"):
|
||||||
standard_help_items = getattr(module, "standard_help_items")
|
standard_help_items = module.standard_help_items
|
||||||
for help_item in standard_help_items:
|
for help_item in standard_help_items:
|
||||||
if label := help_item.get("item_label"):
|
if label := help_item.get("item_label"):
|
||||||
item_type = nav_item.get("item_type")
|
item_type = nav_item.get("item_type")
|
||||||
|
|
|
||||||
|
|
@ -834,10 +834,10 @@ def partial_restore(sql_file_path, verbose=False):
|
||||||
|
|
||||||
warn = click.style(
|
warn = click.style(
|
||||||
"Delete the tables you want to restore manually before attempting"
|
"Delete the tables you want to restore manually before attempting"
|
||||||
" partial restore operation for PostreSQL databases",
|
" partial restore operation for PostgreSQL databases",
|
||||||
fg="yellow",
|
fg="yellow",
|
||||||
)
|
)
|
||||||
warnings.warn(warn)
|
warnings.warn(warn, stacklevel=2)
|
||||||
else:
|
else:
|
||||||
click.secho("Unsupported database type", fg="red")
|
click.secho("Unsupported database type", fg="red")
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ def take_backup_to_dropbox(retry_count=0, upload_db_backup=True):
|
||||||
if isinstance(error_log, str):
|
if isinstance(error_log, str):
|
||||||
error_message = error_log + "\n" + frappe.get_traceback()
|
error_message = error_log + "\n" + frappe.get_traceback()
|
||||||
else:
|
else:
|
||||||
file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log)]
|
file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log, strict=False)]
|
||||||
error_message = "\n".join(file_and_error) + "\n" + frappe.get_traceback()
|
error_message = "\n".join(file_and_error) + "\n" + frappe.get_traceback()
|
||||||
|
|
||||||
send_email(False, "Dropbox", "Dropbox Settings", "send_notifications_to", error_message)
|
send_email(False, "Dropbox", "Dropbox Settings", "send_notifications_to", error_message)
|
||||||
|
|
|
||||||
|
|
@ -489,7 +489,7 @@ class LDAP_TestCase:
|
||||||
@mock_ldap_connection
|
@mock_ldap_connection
|
||||||
def test_get_ldap_attributes(self):
|
def test_get_ldap_attributes(self):
|
||||||
method_return = self.test_class.get_ldap_attributes()
|
method_return = self.test_class.get_ldap_attributes()
|
||||||
self.assertTrue(type(method_return) is list)
|
self.assertTrue(isinstance(method_return, list))
|
||||||
|
|
||||||
@mock_ldap_connection
|
@mock_ldap_connection
|
||||||
def test_fetch_ldap_groups(self):
|
def test_fetch_ldap_groups(self):
|
||||||
|
|
@ -599,7 +599,7 @@ class LDAP_TestCase:
|
||||||
test_ldap_entry = self.connection.entries[0]
|
test_ldap_entry = self.connection.entries[0]
|
||||||
method_return = self.test_class.convert_ldap_entry_to_dict(test_ldap_entry)
|
method_return = self.test_class.convert_ldap_entry_to_dict(test_ldap_entry)
|
||||||
|
|
||||||
self.assertTrue(type(method_return) is dict) # must be dict
|
self.assertTrue(isinstance(method_return, dict)) # must be dict
|
||||||
self.assertTrue(len(method_return) == 6) # there are 6 fields in mock_ldap for use
|
self.assertTrue(len(method_return) == 6) # there are 6 fields in mock_ldap for use
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,7 @@ def frappecloud_migrator(local_site):
|
||||||
request = requests.get(request_url)
|
request = requests.get(request_url)
|
||||||
|
|
||||||
if request.status_code / 100 != 2:
|
if request.status_code / 100 != 2:
|
||||||
print(
|
print(f"Request exited with Status Code: {request.status_code}\nPayload: {html2text(request.text)}")
|
||||||
"Request exitted with Status Code: {}\nPayload: {}".format(
|
|
||||||
request.status_code, html2text(request.text)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
click.secho(
|
click.secho(
|
||||||
"Some errors occurred while recovering the migration script. Please contact us @ Frappe Cloud if this issue persists",
|
"Some errors occurred while recovering the migration script. Please contact us @ Frappe Cloud if this issue persists",
|
||||||
fg="yellow",
|
fg="yellow",
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class GoogleOAuth:
|
||||||
self.domain = domain.lower()
|
self.domain = domain.lower()
|
||||||
self.scopes = (
|
self.scopes = (
|
||||||
" ".join(_SCOPES[self.domain])
|
" ".join(_SCOPES[self.domain])
|
||||||
if isinstance(_SCOPES[self.domain], (list, tuple))
|
if isinstance(_SCOPES[self.domain], list | tuple)
|
||||||
else _SCOPES[self.domain]
|
else _SCOPES[self.domain]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@ def send_email(success, service_name, doctype, email_field, error_status=None):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
subject = "[Warning] Backup Upload Failed"
|
subject = "[Warning] Backup Upload Failed"
|
||||||
message = """
|
message = f"""
|
||||||
<h3>Backup Upload Failed!</h3>
|
<h3>Backup Upload Failed!</h3>
|
||||||
<p>Oops, your automated backup to {} failed.</p>
|
<p>Oops, your automated backup to {service_name} failed.</p>
|
||||||
<p>Error message: {}</p>
|
<p>Error message: {error_status}</p>
|
||||||
<p>Please contact your system manager for more information.</p>""".format(service_name, error_status)
|
<p>Please contact your system manager for more information.</p>"""
|
||||||
|
|
||||||
frappe.sendmail(recipients=recipients, subject=subject, message=message)
|
frappe.sendmail(recipients=recipients, subject=subject, message=message)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -155,5 +155,5 @@ def get_json(obj):
|
||||||
|
|
||||||
|
|
||||||
def json_handler(obj):
|
def json_handler(obj):
|
||||||
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)):
|
if isinstance(obj, datetime.date | datetime.timedelta | datetime.datetime):
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ class BaseDocument:
|
||||||
|
|
||||||
value = self.__dict__.get(key, default)
|
value = self.__dict__.get(key, default)
|
||||||
|
|
||||||
if limit and isinstance(value, (list, tuple)) and len(value) > limit:
|
if limit and isinstance(value, list | tuple) and len(value) > limit:
|
||||||
value = value[:limit]
|
value = value[:limit]
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
@ -396,7 +396,7 @@ class BaseDocument:
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
if convert_dates_to_str and isinstance(
|
if convert_dates_to_str and isinstance(
|
||||||
value, (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)
|
value, datetime.datetime | datetime.date | datetime.time | datetime.timedelta
|
||||||
):
|
):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
|
|
||||||
|
|
@ -1189,7 +1189,7 @@ class BaseDocument:
|
||||||
if not doc:
|
if not doc:
|
||||||
doc = getattr(self, "parent_doc", None) or self
|
doc = getattr(self, "parent_doc", None) or self
|
||||||
|
|
||||||
if (absolute_value or doc.get("absolute_value")) and isinstance(val, (int, float)):
|
if (absolute_value or doc.get("absolute_value")) and isinstance(val, int | float):
|
||||||
val = abs(self.get(fieldname))
|
val = abs(self.get(fieldname))
|
||||||
|
|
||||||
return format_value(val, df=df, doc=doc, currency=currency, format=format)
|
return format_value(val, df=df, doc=doc, currency=currency, format=format)
|
||||||
|
|
@ -1297,7 +1297,7 @@ def _filter(data, filters, limit=None):
|
||||||
for f in filters:
|
for f in filters:
|
||||||
fval = filters[f]
|
fval = filters[f]
|
||||||
|
|
||||||
if not isinstance(fval, (tuple, list)):
|
if not isinstance(fval, tuple | list):
|
||||||
if fval is True:
|
if fval is True:
|
||||||
fval = ("not None", fval)
|
fval = ("not None", fval)
|
||||||
elif fval is False:
|
elif fval is False:
|
||||||
|
|
|
||||||
|
|
@ -222,15 +222,12 @@ class DatabaseQuery:
|
||||||
if frappe.db.db_type == "postgres" and args.order_by and args.group_by:
|
if frappe.db.db_type == "postgres" and args.order_by and args.group_by:
|
||||||
args = self.prepare_select_args(args)
|
args = self.prepare_select_args(args)
|
||||||
|
|
||||||
query = (
|
query = """select {fields}
|
||||||
"""select %(fields)s
|
from {tables}
|
||||||
from %(tables)s
|
{conditions}
|
||||||
%(conditions)s
|
{group_by}
|
||||||
%(group_by)s
|
{order_by}
|
||||||
%(order_by)s
|
{limit}""".format(**args)
|
||||||
%(limit)s"""
|
|
||||||
% args
|
|
||||||
)
|
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
query,
|
query,
|
||||||
|
|
@ -1270,7 +1267,7 @@ def get_between_date_filter(value, df=None):
|
||||||
from_date = frappe.utils.nowdate()
|
from_date = frappe.utils.nowdate()
|
||||||
to_date = frappe.utils.nowdate()
|
to_date = frappe.utils.nowdate()
|
||||||
|
|
||||||
if value and isinstance(value, (list, tuple)):
|
if value and isinstance(value, list | tuple):
|
||||||
if len(value) >= 1:
|
if len(value) >= 1:
|
||||||
from_date = value[0]
|
from_date = value[0]
|
||||||
if len(value) >= 2:
|
if len(value) >= 2:
|
||||||
|
|
|
||||||
|
|
@ -354,8 +354,8 @@ def check_if_doc_is_dynamically_linked(doc, method="Delete"):
|
||||||
|
|
||||||
|
|
||||||
def raise_link_exists_exception(doc, reference_doctype, reference_docname, row=""):
|
def raise_link_exists_exception(doc, reference_doctype, reference_docname, row=""):
|
||||||
doc_link = '<a href="/app/Form/{0}/{1}">{1}</a>'.format(doc.doctype, doc.name)
|
doc_link = f'<a href="/app/Form/{doc.doctype}/{doc.name}">{doc.name}</a>'
|
||||||
reference_link = '<a href="/app/Form/{0}/{1}">{1}</a>'.format(reference_doctype, reference_docname)
|
reference_link = f'<a href="/app/Form/{reference_doctype}/{reference_docname}">{reference_docname}</a>'
|
||||||
|
|
||||||
# hack to display Single doctype only once in message
|
# hack to display Single doctype only once in message
|
||||||
if reference_doctype == reference_docname:
|
if reference_doctype == reference_docname:
|
||||||
|
|
@ -407,12 +407,12 @@ def clear_references(
|
||||||
reference_name_field="reference_name",
|
reference_name_field="reference_name",
|
||||||
):
|
):
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
"""update
|
f"""update
|
||||||
`tab{0}`
|
`tab{doctype}`
|
||||||
set
|
set
|
||||||
{1}=NULL, {2}=NULL
|
{reference_doctype_field}=NULL, {reference_name_field}=NULL
|
||||||
where
|
where
|
||||||
{1}=%s and {2}=%s""".format(doctype, reference_doctype_field, reference_name_field), # nosec
|
{reference_doctype_field}=%s and {reference_name_field}=%s""", # nosec
|
||||||
(reference_doctype, reference_name),
|
(reference_doctype, reference_name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ class Document(BaseDocument):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
get_value_kwargs = {"for_update": self.flags.for_update, "as_dict": True}
|
get_value_kwargs = {"for_update": self.flags.for_update, "as_dict": True}
|
||||||
if not isinstance(self.name, (dict, list)):
|
if not isinstance(self.name, dict | list):
|
||||||
get_value_kwargs["order_by"] = None
|
get_value_kwargs["order_by"] = None
|
||||||
|
|
||||||
d = frappe.db.get_value(
|
d = frappe.db.get_value(
|
||||||
|
|
@ -883,7 +883,7 @@ class Document(BaseDocument):
|
||||||
if not missing:
|
if not missing:
|
||||||
return
|
return
|
||||||
|
|
||||||
for fieldname, msg in missing:
|
for idx, msg in missing: # noqa: B007
|
||||||
msgprint(msg)
|
msgprint(msg)
|
||||||
|
|
||||||
if frappe.flags.print_messages:
|
if frappe.flags.print_messages:
|
||||||
|
|
@ -1252,7 +1252,7 @@ class Document(BaseDocument):
|
||||||
doc_to_compare = frappe.get_doc(self.doctype, amended_from)
|
doc_to_compare = frappe.get_doc(self.doctype, amended_from)
|
||||||
|
|
||||||
version = frappe.new_doc("Version")
|
version = frappe.new_doc("Version")
|
||||||
if is_useful_diff := version.update_version_info(doc_to_compare, self):
|
if version.update_version_info(doc_to_compare, self):
|
||||||
version.insert(ignore_permissions=True)
|
version.insert(ignore_permissions=True)
|
||||||
|
|
||||||
if not frappe.flags.in_migrate:
|
if not frappe.flags.in_migrate:
|
||||||
|
|
@ -1540,7 +1540,7 @@ class Document(BaseDocument):
|
||||||
file_lock.delete_lock(signature)
|
file_lock.delete_lock(signature)
|
||||||
lock_exists = False
|
lock_exists = False
|
||||||
if timeout:
|
if timeout:
|
||||||
for i in range(timeout):
|
for _ in range(timeout):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
if not file_lock.lock_exists(signature):
|
if not file_lock.lock_exists(signature):
|
||||||
lock_exists = False
|
lock_exists = False
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ class Meta(Document):
|
||||||
def serialize(doc):
|
def serialize(doc):
|
||||||
out = {}
|
out = {}
|
||||||
for key, value in doc.__dict__.items():
|
for key, value in doc.__dict__.items():
|
||||||
if isinstance(value, (list, tuple)):
|
if isinstance(value, list | tuple):
|
||||||
if not value or not isinstance(value[0], BaseDocument):
|
if not value or not isinstance(value[0], BaseDocument):
|
||||||
# non standard list object, skip
|
# non standard list object, skip
|
||||||
continue
|
continue
|
||||||
|
|
@ -155,7 +155,7 @@ class Meta(Document):
|
||||||
value = [serialize(d) for d in value]
|
value = [serialize(d) for d in value]
|
||||||
|
|
||||||
if (not no_nulls and value is None) or isinstance(
|
if (not no_nulls and value is None) or isinstance(
|
||||||
value, (str, int, float, datetime, list, tuple)
|
value, str | int | float | datetime | list | tuple
|
||||||
):
|
):
|
||||||
out[key] = value
|
out[key] = value
|
||||||
|
|
||||||
|
|
@ -713,9 +713,7 @@ class Meta(Document):
|
||||||
module_name, "doctype", doctype, "templates", doctype + suffix + ".html"
|
module_name, "doctype", doctype, "templates", doctype + suffix + ".html"
|
||||||
)
|
)
|
||||||
if os.path.exists(template_path):
|
if os.path.exists(template_path):
|
||||||
return "{module_name}/doctype/{doctype_name}/templates/{doctype_name}{suffix}.html".format(
|
return f"{module_name}/doctype/{doctype}/templates/{doctype}{suffix}.html"
|
||||||
module_name=module_name, doctype_name=doctype, suffix=suffix
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def is_nested_set(self):
|
def is_nested_set(self):
|
||||||
|
|
|
||||||
|
|
@ -465,12 +465,10 @@ def append_number_if_name_exists(doctype, value, fieldname="name", separator="-"
|
||||||
|
|
||||||
if exists:
|
if exists:
|
||||||
last = frappe.db.sql(
|
last = frappe.db.sql(
|
||||||
"""SELECT `{fieldname}` FROM `tab{doctype}`
|
f"""SELECT `{fieldname}` FROM `tab{doctype}`
|
||||||
WHERE `{fieldname}` {regex_character} %s
|
WHERE `{fieldname}` {frappe.db.REGEX_CHARACTER} %s
|
||||||
ORDER BY length({fieldname}) DESC,
|
ORDER BY length({fieldname}) DESC,
|
||||||
`{fieldname}` DESC LIMIT 1""".format(
|
`{fieldname}` DESC LIMIT 1""",
|
||||||
doctype=doctype, fieldname=fieldname, regex_character=frappe.db.REGEX_CHARACTER
|
|
||||||
),
|
|
||||||
regex,
|
regex,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ def update_document_title(
|
||||||
|
|
||||||
# TODO: omit this after runtime type checking (ref: https://github.com/frappe/frappe/pull/14927)
|
# TODO: omit this after runtime type checking (ref: https://github.com/frappe/frappe/pull/14927)
|
||||||
for obj in [docname, updated_title, updated_name]:
|
for obj in [docname, updated_title, updated_name]:
|
||||||
if not isinstance(obj, (str, NoneType)):
|
if not isinstance(obj, str | NoneType):
|
||||||
frappe.throw(f"{obj=} must be of type str or None")
|
frappe.throw(f"{obj=} must be of type str or None")
|
||||||
|
|
||||||
# handle bad API usages
|
# handle bad API usages
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
|
||||||
from frappe.build import html_to_js_template
|
from frappe.build import html_to_js_template
|
||||||
from frappe.utils import cstr
|
from frappe.utils import cstr
|
||||||
from frappe.utils.caching import site_cache
|
from frappe.utils.caching import site_cache
|
||||||
|
|
@ -30,9 +29,8 @@ def set_default(doc, key):
|
||||||
frappe.db.set(doc, "is_default", 1)
|
frappe.db.set(doc, "is_default", 1)
|
||||||
|
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
"""update `tab%s` set `is_default`=0
|
"""update `tab{}` set `is_default`=0
|
||||||
where `%s`=%s and name!=%s"""
|
where `{}`={} and name!={}""".format(doc.doctype, key, "%s", "%s"),
|
||||||
% (doc.doctype, key, "%s", "%s"),
|
|
||||||
(doc.get(key), doc.name),
|
(doc.get(key), doc.name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -62,7 +60,7 @@ def render_include(content):
|
||||||
content = cstr(content)
|
content = cstr(content)
|
||||||
|
|
||||||
# try 5 levels of includes
|
# try 5 levels of includes
|
||||||
for i in range(5):
|
for _ in range(5):
|
||||||
if "{% include" in content:
|
if "{% include" in content:
|
||||||
paths = INCLUDE_DIRECTIVE_PATTERN.findall(content)
|
paths = INCLUDE_DIRECTIVE_PATTERN.findall(content)
|
||||||
if not paths:
|
if not paths:
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,8 @@ def rename_field(doctype, old_fieldname, new_fieldname, validate=True):
|
||||||
if new_field.fieldtype in table_fields:
|
if new_field.fieldtype in table_fields:
|
||||||
# change parentfield of table mentioned in options
|
# change parentfield of table mentioned in options
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
"""update `tab%s` set parentfield=%s
|
"""update `tab{}` set parentfield={}
|
||||||
where parentfield=%s"""
|
where parentfield={}""".format(new_field.options.split("\n", 1)[0], "%s", "%s"),
|
||||||
% (new_field.options.split("\n", 1)[0], "%s", "%s"),
|
|
||||||
(new_fieldname, old_fieldname),
|
(new_fieldname, old_fieldname),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -142,9 +141,8 @@ def update_users_report_view_settings(doctype, ref_fieldname, new_fieldname):
|
||||||
|
|
||||||
if columns_modified:
|
if columns_modified:
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
"""update `tabDefaultValue` set defvalue=%s
|
"""update `tabDefaultValue` set defvalue={}
|
||||||
where defkey=%s"""
|
where defkey={}""".format("%s", "%s"),
|
||||||
% ("%s", "%s"),
|
|
||||||
(json.dumps(new_columns), key),
|
(json.dumps(new_columns), key),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ ignore_doctypes = [""]
|
||||||
|
|
||||||
|
|
||||||
def import_files(module, dt=None, dn=None, force=False, pre_process=None, reset_permissions=False):
|
def import_files(module, dt=None, dn=None, force=False, pre_process=None, reset_permissions=False):
|
||||||
if type(module) is list:
|
if isinstance(module, list):
|
||||||
return [
|
return [
|
||||||
import_file(
|
import_file(
|
||||||
m[0],
|
m[0],
|
||||||
|
|
@ -209,11 +209,7 @@ def import_doc(
|
||||||
docdict["__islocal"] = 1
|
docdict["__islocal"] = 1
|
||||||
|
|
||||||
controller = get_controller(docdict["doctype"])
|
controller = get_controller(docdict["doctype"])
|
||||||
if (
|
if controller and hasattr(controller, "prepare_for_import") and callable(controller.prepare_for_import):
|
||||||
controller
|
|
||||||
and hasattr(controller, "prepare_for_import")
|
|
||||||
and callable(getattr(controller, "prepare_for_import"))
|
|
||||||
):
|
|
||||||
controller.prepare_for_import(docdict)
|
controller.prepare_for_import(docdict)
|
||||||
|
|
||||||
doc = frappe.get_doc(docdict)
|
doc = frappe.get_doc(docdict)
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ def split_by_weight(work, weights, chunk_count):
|
||||||
chunk_no = 0
|
chunk_no = 0
|
||||||
chunk_weight = 0
|
chunk_weight = 0
|
||||||
|
|
||||||
for task, weight in zip(work, weights):
|
for task, weight in zip(work, weights, strict=False):
|
||||||
if chunk_weight > expected_weight:
|
if chunk_weight > expected_weight:
|
||||||
chunk_weight = 0
|
chunk_weight = 0
|
||||||
chunk_no += 1
|
chunk_no += 1
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ def execute():
|
||||||
|
|
||||||
for record in duplicateRecords:
|
for record in duplicateRecords:
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
"""delete from `tabUser Permission`
|
f"""delete from `tabUser Permission`
|
||||||
where allow=%s and user=%s and for_value=%s limit {}""".format(record.count - 1),
|
where allow=%s and user=%s and for_value=%s limit {record.count - 1}""",
|
||||||
(record.allow, record.user, record.for_value),
|
(record.allow, record.user, record.for_value),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ def execute():
|
||||||
for user in users:
|
for user in users:
|
||||||
# get user_settings for each user
|
# get user_settings for each user
|
||||||
settings = frappe.db.sql(
|
settings = frappe.db.sql(
|
||||||
"select * from `__UserSettings` \
|
f"select * from `__UserSettings` \
|
||||||
where user={}".format(frappe.db.escape(user.user)),
|
where user={frappe.db.escape(user.user)}",
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ def execute():
|
||||||
phone_values = []
|
phone_values = []
|
||||||
for count, contact_detail in enumerate(contact_details):
|
for count, contact_detail in enumerate(contact_details):
|
||||||
phone_counter = 1
|
phone_counter = 1
|
||||||
is_primary = 1
|
|
||||||
if contact_detail.email_id:
|
if contact_detail.email_id:
|
||||||
email_values.append(
|
email_values.append(
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ def execute():
|
||||||
update_column_table_map.setdefault(field.TABLE_NAME, [])
|
update_column_table_map.setdefault(field.TABLE_NAME, [])
|
||||||
|
|
||||||
update_column_table_map[field.TABLE_NAME].append(
|
update_column_table_map[field.TABLE_NAME].append(
|
||||||
"`{fieldname}`=COALESCE(`{fieldname}`, 0)".format(fieldname=field.COLUMN_NAME)
|
f"`{field.COLUMN_NAME}`=COALESCE(`{field.COLUMN_NAME}`, 0)"
|
||||||
)
|
)
|
||||||
|
|
||||||
for table in frappe.db.get_tables():
|
for table in frappe.db.get_tables():
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ def execute():
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
user_settings = frappe.db.sql(
|
user_settings = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
select
|
select
|
||||||
* from `__UserSettings`
|
* from `__UserSettings`
|
||||||
where
|
where
|
||||||
user='{user}'
|
user='{user.user}'
|
||||||
""".format(user=user.user),
|
""",
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ def has_permission(
|
||||||
meta = frappe.get_meta(doctype)
|
meta = frappe.get_meta(doctype)
|
||||||
|
|
||||||
if doc:
|
if doc:
|
||||||
if isinstance(doc, (str, int)):
|
if isinstance(doc, str | int):
|
||||||
doc = frappe.get_doc(meta.name, doc)
|
doc = frappe.get_doc(meta.name, doc)
|
||||||
perm = get_doc_permissions(doc, user=user, ptype=ptype, debug=debug).get(ptype)
|
perm = get_doc_permissions(doc, user=user, ptype=ptype, debug=debug).get(ptype)
|
||||||
if not perm:
|
if not perm:
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ def get_current_stack_frames():
|
||||||
try:
|
try:
|
||||||
current = inspect.currentframe()
|
current = inspect.currentframe()
|
||||||
frames = inspect.getouterframes(current, context=10)
|
frames = inspect.getouterframes(current, context=10)
|
||||||
for frame, filename, lineno, function, context, index in list(reversed(frames))[:-2]:
|
for frame, filename, lineno, function, context, index in list(reversed(frames))[:-2]: # noqa: B007
|
||||||
if "/apps/" in filename or SERVER_SCRIPT_FILE_PREFIX in filename:
|
if "/apps/" in filename or SERVER_SCRIPT_FILE_PREFIX in filename:
|
||||||
yield {
|
yield {
|
||||||
"filename": TRACEBACK_PATH_PATTERN.sub("", filename),
|
"filename": TRACEBACK_PATH_PATTERN.sub("", filename),
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,7 @@ def get_user_energy_and_review_points(user=None, from_date=None, as_dict=True):
|
||||||
values.from_date = from_date
|
values.from_date = from_date
|
||||||
|
|
||||||
points_list = frappe.db.sql(
|
points_list = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
SUM(CASE WHEN `type` != 'Review' THEN `points` ELSE 0 END) AS energy_points,
|
SUM(CASE WHEN `type` != 'Review' THEN `points` ELSE 0 END) AS energy_points,
|
||||||
SUM(CASE WHEN `type` = 'Review' THEN `points` ELSE 0 END) AS review_points,
|
SUM(CASE WHEN `type` = 'Review' THEN `points` ELSE 0 END) AS review_points,
|
||||||
|
|
@ -271,7 +271,7 @@ def get_user_energy_and_review_points(user=None, from_date=None, as_dict=True):
|
||||||
{conditions}
|
{conditions}
|
||||||
GROUP BY `user`
|
GROUP BY `user`
|
||||||
ORDER BY `energy_points` DESC
|
ORDER BY `energy_points` DESC
|
||||||
""".format(conditions=conditions, given_points_condition=given_points_condition),
|
""",
|
||||||
values=values,
|
values=values,
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class EnergyPointRule(Document):
|
||||||
{"points": points, "user": user, "rule": rule},
|
{"points": points, "user": user, "rule": rule},
|
||||||
self.apply_only_once,
|
self.apply_only_once,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
self.log_error("Energy points failed")
|
self.log_error("Energy points failed")
|
||||||
|
|
||||||
def rule_condition_satisfied(self, doc):
|
def rule_condition_satisfied(self, doc):
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@ def run_tests_for_doctype(
|
||||||
junit_xml_output=False,
|
junit_xml_output=False,
|
||||||
):
|
):
|
||||||
modules = []
|
modules = []
|
||||||
if not isinstance(doctypes, (list, tuple)):
|
if not isinstance(doctypes, list | tuple):
|
||||||
doctypes = [doctypes]
|
doctypes = [doctypes]
|
||||||
|
|
||||||
for doctype in doctypes:
|
for doctype in doctypes:
|
||||||
|
|
@ -268,7 +268,7 @@ def _run_unittest(
|
||||||
|
|
||||||
test_suite = unittest.TestSuite()
|
test_suite = unittest.TestSuite()
|
||||||
|
|
||||||
if not isinstance(modules, (list, tuple)):
|
if not isinstance(modules, list | tuple):
|
||||||
modules = [modules]
|
modules = [modules]
|
||||||
|
|
||||||
for module in modules:
|
for module in modules:
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,9 @@ def patch_request_header(key, *args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
class ThreadWithReturnValue(Thread):
|
class ThreadWithReturnValue(Thread):
|
||||||
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, site=None):
|
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, site=None):
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = {}
|
||||||
Thread.__init__(self, group, target, name, args, kwargs)
|
Thread.__init__(self, group, target, name, args, kwargs)
|
||||||
self._return = None
|
self._return = None
|
||||||
self.site = site or _site
|
self.site = site or _site
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ register_with_external_service = MagicMock(return_value=True)
|
||||||
def request_specific_api(a: list | tuple | dict | int, b: int) -> int:
|
def request_specific_api(a: list | tuple | dict | int, b: int) -> int:
|
||||||
# API that takes very long to return a result
|
# API that takes very long to return a result
|
||||||
todays_value = external_service()
|
todays_value = external_service()
|
||||||
if not isinstance(a, (int, float)):
|
if not isinstance(a, int | float):
|
||||||
a = 1
|
a = 1
|
||||||
return a**b * todays_value
|
return a**b * todays_value
|
||||||
|
|
||||||
|
|
@ -44,7 +44,9 @@ class TestCachingUtils(FrappeTestCase):
|
||||||
frappe.get_last_doc("DocType"),
|
frappe.get_last_doc("DocType"),
|
||||||
frappe._dict(),
|
frappe._dict(),
|
||||||
]
|
]
|
||||||
same_output_received = lambda: all([x for x in set(retval) if x == retval[0]])
|
|
||||||
|
def same_output_received():
|
||||||
|
return all([x for x in set(retval) if x == retval[0]])
|
||||||
|
|
||||||
# ensure that external service was called only once
|
# ensure that external service was called only once
|
||||||
# thereby return value of request_specific_api is cached
|
# thereby return value of request_specific_api is cached
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
# License: MIT. See LICENSE
|
# License: MIT. See LICENSE
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import inspect
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from random import choice
|
from random import choice
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
@ -17,7 +16,7 @@ from frappe.query_builder import Field
|
||||||
from frappe.query_builder.functions import Concat_ws
|
from frappe.query_builder.functions import Concat_ws
|
||||||
from frappe.tests.test_query_builder import db_type_is, run_only_if
|
from frappe.tests.test_query_builder import db_type_is, run_only_if
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, cint, now, random_string, set_request
|
from frappe.utils import add_days, now, random_string, set_request
|
||||||
from frappe.utils.testutils import clear_custom_fields
|
from frappe.utils.testutils import clear_custom_fields
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -437,7 +436,7 @@ class TestDB(FrappeTestCase):
|
||||||
frappe.db.MAX_WRITES_PER_TRANSACTION = 1
|
frappe.db.MAX_WRITES_PER_TRANSACTION = 1
|
||||||
note = frappe.get_last_doc("ToDo")
|
note = frappe.get_last_doc("ToDo")
|
||||||
note.description = "changed"
|
note.description = "changed"
|
||||||
with self.assertRaises(frappe.TooManyWritesError) as tmw:
|
with self.assertRaises(frappe.TooManyWritesError):
|
||||||
note.save()
|
note.save()
|
||||||
|
|
||||||
frappe.db.MAX_WRITES_PER_TRANSACTION = Database.MAX_WRITES_PER_TRANSACTION
|
frappe.db.MAX_WRITES_PER_TRANSACTION = Database.MAX_WRITES_PER_TRANSACTION
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,11 @@ class TestDBQuery(FrappeTestCase):
|
||||||
db_query = DatabaseQuery("DocType")
|
db_query = DatabaseQuery("DocType")
|
||||||
add_custom_field("DocType", "test_tab_field", "Data")
|
add_custom_field("DocType", "test_tab_field", "Data")
|
||||||
|
|
||||||
db_query.fields = ["tabNote.creation", "test_tab_field", "tabDocType.test_tab_field"]
|
db_query.fields = [
|
||||||
|
"tabNote.creation",
|
||||||
|
"test_tab_field",
|
||||||
|
"tabDocType.test_tab_field",
|
||||||
|
]
|
||||||
db_query.extract_tables()
|
db_query.extract_tables()
|
||||||
self.assertIn("`tabNote`", db_query.tables)
|
self.assertIn("`tabNote`", db_query.tables)
|
||||||
self.assertIn("`tabDocType`", db_query.tables)
|
self.assertIn("`tabDocType`", db_query.tables)
|
||||||
|
|
@ -148,7 +152,10 @@ class TestDBQuery(FrappeTestCase):
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
doctype="Parent DocType 1",
|
doctype="Parent DocType 1",
|
||||||
title="test",
|
title="test",
|
||||||
child=[{"title": "parent 1 child record 1"}, {"title": "parent 1 child record 2"}],
|
child=[
|
||||||
|
{"title": "parent 1 child record 1"},
|
||||||
|
{"title": "parent 1 child record 2"},
|
||||||
|
],
|
||||||
__newname="test_parent",
|
__newname="test_parent",
|
||||||
).insert(ignore_if_duplicate=True)
|
).insert(ignore_if_duplicate=True)
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
|
|
@ -295,7 +302,8 @@ class TestDBQuery(FrappeTestCase):
|
||||||
|
|
||||||
# if both from and to_date values are passed
|
# if both from and to_date values are passed
|
||||||
data = DatabaseQuery("Event").execute(
|
data = DatabaseQuery("Event").execute(
|
||||||
filters={"starts_on": ["between", ["2016-07-06", "2016-07-07"]]}, fields=["name"]
|
filters={"starts_on": ["between", ["2016-07-06", "2016-07-07"]]},
|
||||||
|
fields=["name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertIn({"name": event2.name}, data)
|
self.assertIn({"name": event2.name}, data)
|
||||||
|
|
@ -316,7 +324,8 @@ class TestDBQuery(FrappeTestCase):
|
||||||
|
|
||||||
# test between is formatted for creation column
|
# test between is formatted for creation column
|
||||||
data = DatabaseQuery("Event").execute(
|
data = DatabaseQuery("Event").execute(
|
||||||
filters={"creation": ["between", ["2016-07-06", "2016-07-07"]]}, fields=["name"]
|
filters={"creation": ["between", ["2016-07-06", "2016-07-07"]]},
|
||||||
|
fields=["name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_between_filters_date_bounds(self):
|
def test_between_filters_date_bounds(self):
|
||||||
|
|
@ -368,7 +377,10 @@ class TestDBQuery(FrappeTestCase):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
frappe.DataError,
|
frappe.DataError,
|
||||||
DatabaseQuery("DocType").execute,
|
DatabaseQuery("DocType").execute,
|
||||||
fields=["name", "issingle, IF(issingle=1, (select name from tabUser), count(name))"],
|
fields=[
|
||||||
|
"name",
|
||||||
|
"issingle, IF(issingle=1, (select name from tabUser), count(name))",
|
||||||
|
],
|
||||||
limit_start=0,
|
limit_start=0,
|
||||||
limit_page_length=1,
|
limit_page_length=1,
|
||||||
)
|
)
|
||||||
|
|
@ -392,7 +404,10 @@ class TestDBQuery(FrappeTestCase):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
frappe.DataError,
|
frappe.DataError,
|
||||||
DatabaseQuery("DocType").execute,
|
DatabaseQuery("DocType").execute,
|
||||||
fields=["name", "issingle, IF(issingle=1, (SELECT name from tabUser), count(*))"],
|
fields=[
|
||||||
|
"name",
|
||||||
|
"issingle, IF(issingle=1, (SELECT name from tabUser), count(*))",
|
||||||
|
],
|
||||||
limit_start=0,
|
limit_start=0,
|
||||||
limit_page_length=1,
|
limit_page_length=1,
|
||||||
)
|
)
|
||||||
|
|
@ -466,14 +481,20 @@ class TestDBQuery(FrappeTestCase):
|
||||||
self.assertTrue("_relevance" in data[0])
|
self.assertTrue("_relevance" in data[0])
|
||||||
|
|
||||||
data = DatabaseQuery("DocType").execute(
|
data = DatabaseQuery("DocType").execute(
|
||||||
fields=["name", "issingle", "date(creation) as creation"], limit_start=0, limit_page_length=1
|
fields=["name", "issingle", "date(creation) as creation"],
|
||||||
|
limit_start=0,
|
||||||
|
limit_page_length=1,
|
||||||
)
|
)
|
||||||
self.assertTrue("creation" in data[0])
|
self.assertTrue("creation" in data[0])
|
||||||
|
|
||||||
if frappe.db.db_type != "postgres":
|
if frappe.db.db_type != "postgres":
|
||||||
# datediff function does not exist in postgres
|
# datediff function does not exist in postgres
|
||||||
data = DatabaseQuery("DocType").execute(
|
data = DatabaseQuery("DocType").execute(
|
||||||
fields=["name", "issingle", "datediff(modified, creation) as date_diff"],
|
fields=[
|
||||||
|
"name",
|
||||||
|
"issingle",
|
||||||
|
"datediff(modified, creation) as date_diff",
|
||||||
|
],
|
||||||
limit_start=0,
|
limit_start=0,
|
||||||
limit_page_length=1,
|
limit_page_length=1,
|
||||||
)
|
)
|
||||||
|
|
@ -481,14 +502,22 @@ class TestDBQuery(FrappeTestCase):
|
||||||
|
|
||||||
with self.assertRaises(frappe.DataError):
|
with self.assertRaises(frappe.DataError):
|
||||||
DatabaseQuery("DocType").execute(
|
DatabaseQuery("DocType").execute(
|
||||||
fields=["name", "issingle", "if (issingle=1, (select name from tabUser), count(name))"],
|
fields=[
|
||||||
|
"name",
|
||||||
|
"issingle",
|
||||||
|
"if (issingle=1, (select name from tabUser), count(name))",
|
||||||
|
],
|
||||||
limit_start=0,
|
limit_start=0,
|
||||||
limit_page_length=1,
|
limit_page_length=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assertRaises(frappe.DataError):
|
with self.assertRaises(frappe.DataError):
|
||||||
DatabaseQuery("DocType").execute(
|
DatabaseQuery("DocType").execute(
|
||||||
fields=["name", "issingle", "if(issingle=1, (select name from tabUser), count(name))"],
|
fields=[
|
||||||
|
"name",
|
||||||
|
"issingle",
|
||||||
|
"if(issingle=1, (select name from tabUser), count(name))",
|
||||||
|
],
|
||||||
limit_start=0,
|
limit_start=0,
|
||||||
limit_page_length=1,
|
limit_page_length=1,
|
||||||
)
|
)
|
||||||
|
|
@ -578,7 +607,10 @@ class TestDBQuery(FrappeTestCase):
|
||||||
DatabaseQuery("DocType").execute,
|
DatabaseQuery("DocType").execute,
|
||||||
fields=["name"],
|
fields=["name"],
|
||||||
filters={"editable_grid,": 1},
|
filters={"editable_grid,": 1},
|
||||||
or_filters=[["DocType", "istable", "=", 1], ["DocType", "beta and 1=1", "=", 0]],
|
or_filters=[
|
||||||
|
["DocType", "istable", "=", 1],
|
||||||
|
["DocType", "beta and 1=1", "=", 0],
|
||||||
|
],
|
||||||
limit_start=0,
|
limit_start=0,
|
||||||
limit_page_length=1,
|
limit_page_length=1,
|
||||||
)
|
)
|
||||||
|
|
@ -600,13 +632,18 @@ class TestDBQuery(FrappeTestCase):
|
||||||
self.assertTrue("Role Permission for Page and Report" in [d["name"] for d in out])
|
self.assertTrue("Role Permission for Page and Report" in [d["name"] for d in out])
|
||||||
|
|
||||||
out = DatabaseQuery("DocType").execute(
|
out = DatabaseQuery("DocType").execute(
|
||||||
fields=["name"], filters={"track_changes": 1, "module": "Core"}, order_by="creation"
|
fields=["name"],
|
||||||
|
filters={"track_changes": 1, "module": "Core"},
|
||||||
|
order_by="creation",
|
||||||
)
|
)
|
||||||
self.assertTrue("File" in [d["name"] for d in out])
|
self.assertTrue("File" in [d["name"] for d in out])
|
||||||
|
|
||||||
out = DatabaseQuery("DocType").execute(
|
out = DatabaseQuery("DocType").execute(
|
||||||
fields=["name"],
|
fields=["name"],
|
||||||
filters=[["DocType", "ifnull(track_changes, 0)", "=", 0], ["DocType", "module", "=", "Core"]],
|
filters=[
|
||||||
|
["DocType", "ifnull(track_changes, 0)", "=", 0],
|
||||||
|
["DocType", "module", "=", "Core"],
|
||||||
|
],
|
||||||
order_by="creation",
|
order_by="creation",
|
||||||
)
|
)
|
||||||
self.assertTrue("DefaultValue" in [d["name"] for d in out])
|
self.assertTrue("DefaultValue" in [d["name"] for d in out])
|
||||||
|
|
@ -743,7 +780,7 @@ class TestDBQuery(FrappeTestCase):
|
||||||
def test_set_field_tables(self):
|
def test_set_field_tables(self):
|
||||||
# Tests _in_standard_sql_methods method in test_set_field_tables
|
# Tests _in_standard_sql_methods method in test_set_field_tables
|
||||||
# The following query will break if the above method is broken
|
# The following query will break if the above method is broken
|
||||||
data = frappe.db.get_list(
|
frappe.db.get_list(
|
||||||
"Web Form",
|
"Web Form",
|
||||||
filters=[["Web Form Field", "reqd", "=", 1]],
|
filters=[["Web Form Field", "reqd", "=", 1]],
|
||||||
fields=["count(*) as count"],
|
fields=["count(*) as count"],
|
||||||
|
|
@ -755,7 +792,7 @@ class TestDBQuery(FrappeTestCase):
|
||||||
try:
|
try:
|
||||||
frappe.get_list("Prepared Report", ["*"])
|
frappe.get_list("Prepared Report", ["*"])
|
||||||
frappe.get_list("Scheduled Job Type", ["*"])
|
frappe.get_list("Scheduled Job Type", ["*"])
|
||||||
except Exception as e:
|
except Exception:
|
||||||
print(frappe.get_traceback())
|
print(frappe.get_traceback())
|
||||||
self.fail("get_list not working with virtual field")
|
self.fail("get_list not working with virtual field")
|
||||||
|
|
||||||
|
|
@ -805,21 +842,30 @@ class TestDBQuery(FrappeTestCase):
|
||||||
def test_permlevel_fields(self):
|
def test_permlevel_fields(self):
|
||||||
with setup_patched_blog_post(), setup_test_user(set_user=True):
|
with setup_patched_blog_post(), setup_test_user(set_user=True):
|
||||||
data = frappe.get_list(
|
data = frappe.get_list(
|
||||||
"Blog Post", filters={"published": 1}, fields=["name", "published"], limit=1
|
"Blog Post",
|
||||||
|
filters={"published": 1},
|
||||||
|
fields=["name", "published"],
|
||||||
|
limit=1,
|
||||||
)
|
)
|
||||||
self.assertFalse("published" in data[0])
|
self.assertFalse("published" in data[0])
|
||||||
self.assertTrue("name" in data[0])
|
self.assertTrue("name" in data[0])
|
||||||
self.assertEqual(len(data[0]), 1)
|
self.assertEqual(len(data[0]), 1)
|
||||||
|
|
||||||
data = frappe.get_list(
|
data = frappe.get_list(
|
||||||
"Blog Post", filters={"published": 1}, fields=["name", "`published`"], limit=1
|
"Blog Post",
|
||||||
|
filters={"published": 1},
|
||||||
|
fields=["name", "`published`"],
|
||||||
|
limit=1,
|
||||||
)
|
)
|
||||||
self.assertFalse("published" in data[0])
|
self.assertFalse("published" in data[0])
|
||||||
self.assertTrue("name" in data[0])
|
self.assertTrue("name" in data[0])
|
||||||
self.assertEqual(len(data[0]), 1)
|
self.assertEqual(len(data[0]), 1)
|
||||||
|
|
||||||
data = frappe.get_list(
|
data = frappe.get_list(
|
||||||
"Blog Post", filters={"published": 1}, fields=["name", "`tabBlog Post`.`published`"], limit=1
|
"Blog Post",
|
||||||
|
filters={"published": 1},
|
||||||
|
fields=["name", "`tabBlog Post`.`published`"],
|
||||||
|
limit=1,
|
||||||
)
|
)
|
||||||
self.assertFalse("published" in data[0])
|
self.assertFalse("published" in data[0])
|
||||||
self.assertTrue("name" in data[0])
|
self.assertTrue("name" in data[0])
|
||||||
|
|
@ -836,13 +882,19 @@ class TestDBQuery(FrappeTestCase):
|
||||||
self.assertEqual(len(data[0]), 1)
|
self.assertEqual(len(data[0]), 1)
|
||||||
|
|
||||||
data = frappe.get_list(
|
data = frappe.get_list(
|
||||||
"Blog Post", filters={"published": 1}, fields=["name", "MAX(`published`)"], limit=1
|
"Blog Post",
|
||||||
|
filters={"published": 1},
|
||||||
|
fields=["name", "MAX(`published`)"],
|
||||||
|
limit=1,
|
||||||
)
|
)
|
||||||
self.assertTrue("name" in data[0])
|
self.assertTrue("name" in data[0])
|
||||||
self.assertEqual(len(data[0]), 1)
|
self.assertEqual(len(data[0]), 1)
|
||||||
|
|
||||||
data = frappe.get_list(
|
data = frappe.get_list(
|
||||||
"Blog Post", filters={"published": 1}, fields=["name", "LAST(published)"], limit=1
|
"Blog Post",
|
||||||
|
filters={"published": 1},
|
||||||
|
fields=["name", "LAST(published)"],
|
||||||
|
limit=1,
|
||||||
)
|
)
|
||||||
self.assertTrue("name" in data[0])
|
self.assertTrue("name" in data[0])
|
||||||
self.assertEqual(len(data[0]), 1)
|
self.assertEqual(len(data[0]), 1)
|
||||||
|
|
@ -858,12 +910,20 @@ class TestDBQuery(FrappeTestCase):
|
||||||
self.assertEqual(len(data[0]), 2)
|
self.assertEqual(len(data[0]), 2)
|
||||||
|
|
||||||
data = frappe.get_list(
|
data = frappe.get_list(
|
||||||
"Blog Post", filters={"published": 1}, fields=["name", "now() abhi"], limit=1
|
"Blog Post",
|
||||||
|
filters={"published": 1},
|
||||||
|
fields=["name", "now() abhi"],
|
||||||
|
limit=1,
|
||||||
)
|
)
|
||||||
self.assertIsInstance(data[0]["abhi"], datetime.datetime)
|
self.assertIsInstance(data[0]["abhi"], datetime.datetime)
|
||||||
self.assertEqual(len(data[0]), 2)
|
self.assertEqual(len(data[0]), 2)
|
||||||
|
|
||||||
data = frappe.get_list("Blog Post", filters={"published": 1}, fields=["name", "'LABEL'"], limit=1)
|
data = frappe.get_list(
|
||||||
|
"Blog Post",
|
||||||
|
filters={"published": 1},
|
||||||
|
fields=["name", "'LABEL'"],
|
||||||
|
limit=1,
|
||||||
|
)
|
||||||
self.assertTrue("name" in data[0])
|
self.assertTrue("name" in data[0])
|
||||||
self.assertTrue("LABEL" in data[0].values())
|
self.assertTrue("LABEL" in data[0].values())
|
||||||
self.assertEqual(len(data[0]), 2)
|
self.assertEqual(len(data[0]), 2)
|
||||||
|
|
@ -892,7 +952,11 @@ class TestDBQuery(FrappeTestCase):
|
||||||
|
|
||||||
data = frappe.get_list(
|
data = frappe.get_list(
|
||||||
"Blog Post",
|
"Blog Post",
|
||||||
fields=["name", "blogger.full_name as blogger_full_name", "blog_category.description"],
|
fields=[
|
||||||
|
"name",
|
||||||
|
"blogger.full_name as blogger_full_name",
|
||||||
|
"blog_category.description",
|
||||||
|
],
|
||||||
limit=1,
|
limit=1,
|
||||||
)
|
)
|
||||||
self.assertTrue("name" in data[0])
|
self.assertTrue("name" in data[0])
|
||||||
|
|
@ -906,7 +970,11 @@ class TestDBQuery(FrappeTestCase):
|
||||||
dt = new_doctype("autoinc_dt_test", autoname="autoincrement").insert(ignore_permissions=True)
|
dt = new_doctype("autoinc_dt_test", autoname="autoincrement").insert(ignore_permissions=True)
|
||||||
|
|
||||||
query = DatabaseQuery("autoinc_dt_test").execute(
|
query = DatabaseQuery("autoinc_dt_test").execute(
|
||||||
fields=["locate('1', `tabautoinc_dt_test`.`name`)", "name", "locate('1', name)"],
|
fields=[
|
||||||
|
"locate('1', `tabautoinc_dt_test`.`name`)",
|
||||||
|
"name",
|
||||||
|
"locate('1', name)",
|
||||||
|
],
|
||||||
filters={"name": 1},
|
filters={"name": 1},
|
||||||
run=False,
|
run=False,
|
||||||
)
|
)
|
||||||
|
|
@ -929,7 +997,9 @@ class TestDBQuery(FrappeTestCase):
|
||||||
frappe.delete_doc_if_exists("DocType", "table_dt")
|
frappe.delete_doc_if_exists("DocType", "table_dt")
|
||||||
|
|
||||||
table_dt = new_doctype(
|
table_dt = new_doctype(
|
||||||
"table_dt", istable=1, fields=[{"label": "1field", "fieldname": "2field", "fieldtype": "Data"}]
|
"table_dt",
|
||||||
|
istable=1,
|
||||||
|
fields=[{"label": "1field", "fieldname": "2field", "fieldtype": "Data"}],
|
||||||
).insert()
|
).insert()
|
||||||
|
|
||||||
dt = new_doctype(
|
dt = new_doctype(
|
||||||
|
|
@ -974,7 +1044,9 @@ class TestDBQuery(FrappeTestCase):
|
||||||
table_dt.delete()
|
table_dt.delete()
|
||||||
|
|
||||||
def test_permission_query_condition(self):
|
def test_permission_query_condition(self):
|
||||||
from frappe.desk.doctype.dashboard_settings.dashboard_settings import create_dashboard_settings
|
from frappe.desk.doctype.dashboard_settings.dashboard_settings import (
|
||||||
|
create_dashboard_settings,
|
||||||
|
)
|
||||||
|
|
||||||
self.doctype = "Dashboard Settings"
|
self.doctype = "Dashboard Settings"
|
||||||
self.user = "test'5@example.com"
|
self.user = "test'5@example.com"
|
||||||
|
|
@ -984,11 +1056,11 @@ class TestDBQuery(FrappeTestCase):
|
||||||
create_dashboard_settings(self.user)
|
create_dashboard_settings(self.user)
|
||||||
|
|
||||||
dashboard_settings = frappe.db.sql(
|
dashboard_settings = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT name
|
SELECT name
|
||||||
FROM `tabDashboard Settings`
|
FROM `tabDashboard Settings`
|
||||||
WHERE {condition}
|
WHERE {permission_query_conditions}
|
||||||
""".format(condition=permission_query_conditions),
|
""",
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
|
|
@ -1006,7 +1078,10 @@ class TestDBQuery(FrappeTestCase):
|
||||||
def get_list(args):
|
def get_list(args):
|
||||||
...
|
...
|
||||||
|
|
||||||
with patch("frappe.controllers", new={frappe.local.site: {"Virtual DocType": VirtualDocType}}):
|
with patch(
|
||||||
|
"frappe.controllers",
|
||||||
|
new={frappe.local.site: {"Virtual DocType": VirtualDocType}},
|
||||||
|
):
|
||||||
VirtualDocType.get_list = MagicMock()
|
VirtualDocType.get_list = MagicMock()
|
||||||
|
|
||||||
frappe.get_all("Virtual DocType", filters={"name": "test"}, fields=["name"], limit=1)
|
frappe.get_all("Virtual DocType", filters={"name": "test"}, fields=["name"], limit=1)
|
||||||
|
|
@ -1120,7 +1195,11 @@ class TestReportView(FrappeTestCase):
|
||||||
)
|
)
|
||||||
list_filter_response = execute_cmd("frappe.desk.reportview.get_count")
|
list_filter_response = execute_cmd("frappe.desk.reportview.get_count")
|
||||||
frappe.local.form_dict = frappe._dict(
|
frappe.local.form_dict = frappe._dict(
|
||||||
{"doctype": "DocType", "filters": {"show_title_field_in_link": 1}, "distinct": "true"}
|
{
|
||||||
|
"doctype": "DocType",
|
||||||
|
"filters": {"show_title_field_in_link": 1},
|
||||||
|
"distinct": "true",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
dict_filter_response = execute_cmd("frappe.desk.reportview.get_count")
|
dict_filter_response = execute_cmd("frappe.desk.reportview.get_count")
|
||||||
self.assertIsInstance(list_filter_response, int)
|
self.assertIsInstance(list_filter_response, int)
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ class TestOAuth20(FrappeRequestTestCase):
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
def test_openid_code_id_token(self):
|
def test_openid_code_id_token(self):
|
||||||
client = update_client_for_auth_code_grant(self.client_id)
|
update_client_for_auth_code_grant(self.client_id)
|
||||||
nonce = frappe.generate_hash()
|
nonce = frappe.generate_hash()
|
||||||
|
|
||||||
# Go to Authorize url
|
# Go to Authorize url
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ def check_patch_files(app):
|
||||||
missing_patches.append(module)
|
missing_patches.append(module)
|
||||||
|
|
||||||
if missing_patches:
|
if missing_patches:
|
||||||
raise Exception(f"Patches missing in patch.txt: \n" + "\n".join(missing_patches))
|
raise Exception("Patches missing in patch.txt: \n" + "\n".join(missing_patches))
|
||||||
|
|
||||||
|
|
||||||
def _get_dotted_path(file: Path, app) -> str:
|
def _get_dotted_path(file: Path, app) -> str:
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ class TestPerformance(FrappeTestCase):
|
||||||
self.assertGreaterEqual(
|
self.assertGreaterEqual(
|
||||||
rps,
|
rps,
|
||||||
EXPECTED_RPS * (1 - FAILURE_THREASHOLD),
|
EXPECTED_RPS * (1 - FAILURE_THREASHOLD),
|
||||||
f"Possible performance regression in basic /api/Resource list requests",
|
"Possible performance regression in basic /api/Resource list requests",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_homepage_resolver(self):
|
def test_homepage_resolver(self):
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.utils
|
import frappe.utils
|
||||||
from frappe.core.doctype.doctype.test_doctype import new_doctype
|
|
||||||
from frappe.desk.query_report import build_xlsx_data, export_query, run
|
from frappe.desk.query_report import build_xlsx_data, export_query, run
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils.xlsxutils import make_xlsx
|
from frappe.utils.xlsxutils import make_xlsx
|
||||||
|
|
@ -138,7 +137,7 @@ class TestQueryReport(FrappeTestCase):
|
||||||
{"label": "First Name", "fieldname": "first_name", "fieldtype": "Data"},
|
{"label": "First Name", "fieldname": "first_name", "fieldtype": "Data"},
|
||||||
{"label": "Last Name", "fieldname": "last_name", "fieldtype": "Data"},
|
{"label": "Last Name", "fieldname": "last_name", "fieldtype": "Data"},
|
||||||
]
|
]
|
||||||
docA = frappe.get_doc(
|
frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"name": "Doc A",
|
"name": "Doc A",
|
||||||
|
|
@ -150,7 +149,7 @@ class TestQueryReport(FrappeTestCase):
|
||||||
}
|
}
|
||||||
).insert(ignore_if_duplicate=True)
|
).insert(ignore_if_duplicate=True)
|
||||||
|
|
||||||
docB = frappe.get_doc(
|
frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"name": "Doc B",
|
"name": "Doc B",
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ class TestRecorder(FrappeTestCase):
|
||||||
|
|
||||||
self.assertEqual(len(request["calls"]), len(queries))
|
self.assertEqual(len(request["calls"]), len(queries))
|
||||||
|
|
||||||
for query, call in zip(queries, request["calls"]):
|
for query, call in zip(queries, request["calls"], strict=False):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
call["query"],
|
call["query"],
|
||||||
sqlparse.format(
|
sqlparse.format(
|
||||||
|
|
@ -134,7 +134,7 @@ class TestRecorder(FrappeTestCase):
|
||||||
requests = frappe.recorder.get()
|
requests = frappe.recorder.get()
|
||||||
request = frappe.recorder.get(requests[0]["uuid"])
|
request = frappe.recorder.get(requests[0]["uuid"])
|
||||||
|
|
||||||
for query, call in zip(queries, request["calls"]):
|
for query, call in zip(queries, request["calls"], strict=False):
|
||||||
self.assertEqual(call["exact_copies"], query[1])
|
self.assertEqual(call["exact_copies"], query[1])
|
||||||
|
|
||||||
def test_error_page_rendering(self):
|
def test_error_page_rendering(self):
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ class TestSafeExec(FrappeTestCase):
|
||||||
def test_ensure_getattrable_globals(self):
|
def test_ensure_getattrable_globals(self):
|
||||||
def check_safe(objects):
|
def check_safe(objects):
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
if isinstance(obj, (types.ModuleType, types.CodeType, types.TracebackType, types.FrameType)):
|
if isinstance(obj, types.ModuleType | types.CodeType | types.TracebackType | types.FrameType):
|
||||||
self.fail(f"{obj} wont work in safe exec.")
|
self.fail(f"{obj} wont work in safe exec.")
|
||||||
elif isinstance(obj, dict):
|
elif isinstance(obj, dict):
|
||||||
check_safe(obj.values())
|
check_safe(obj.values())
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class TestTranslate(FrappeTestCase):
|
||||||
msg=f"Mismatched output:\nExpected: {expected_output}\nFound: {data}",
|
msg=f"Mismatched output:\nExpected: {expected_output}\nFound: {data}",
|
||||||
)
|
)
|
||||||
|
|
||||||
for extracted, expected in zip(data, expected_output):
|
for extracted, expected in zip(data, expected_output, strict=False):
|
||||||
ext_filename, ext_message, ext_context, ext_line = extracted
|
ext_filename, ext_message, ext_context, ext_line = extracted
|
||||||
exp_message, exp_context, exp_line = expected
|
exp_message, exp_context, exp_line = expected
|
||||||
self.assertEqual(ext_filename, exp_filename)
|
self.assertEqual(ext_filename, exp_filename)
|
||||||
|
|
@ -214,7 +214,7 @@ class TestTranslate(FrappeTestCase):
|
||||||
|
|
||||||
output = extract_messages_from_python_code(code)
|
output = extract_messages_from_python_code(code)
|
||||||
self.assertEqual(len(expected_output), len(output))
|
self.assertEqual(len(expected_output), len(output))
|
||||||
for expected, actual in zip(expected_output, output):
|
for expected, actual in zip(expected_output, output, strict=False):
|
||||||
with self.subTest():
|
with self.subTest():
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
|
@ -251,7 +251,7 @@ class TestTranslate(FrappeTestCase):
|
||||||
output = extract_messages_from_javascript_code(code)
|
output = extract_messages_from_javascript_code(code)
|
||||||
|
|
||||||
self.assertEqual(len(expected_output), len(output))
|
self.assertEqual(len(expected_output), len(output))
|
||||||
for expected, actual in zip(expected_output, output):
|
for expected, actual in zip(expected_output, output, strict=False):
|
||||||
with self.subTest():
|
with self.subTest():
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -232,7 +232,7 @@ def toggle_2fa_all_role(state=None):
|
||||||
"""Enable or disable 2fa for 'all' role on the system."""
|
"""Enable or disable 2fa for 'all' role on the system."""
|
||||||
all_role = frappe.get_doc("Role", "All")
|
all_role = frappe.get_doc("Role", "All")
|
||||||
state = state if state is not None else False
|
state = state if state is not None else False
|
||||||
if type(state) != bool:
|
if not isinstance(state, bool):
|
||||||
return
|
return
|
||||||
|
|
||||||
all_role.two_factor_auth = cint(state)
|
all_role.two_factor_auth = cint(state)
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,11 @@ from frappe.utils import (
|
||||||
validate_phone_number_with_country_code,
|
validate_phone_number_with_country_code,
|
||||||
validate_url,
|
validate_url,
|
||||||
)
|
)
|
||||||
from frappe.utils.change_log import check_release_on_github, get_remote_url, parse_github_url
|
from frappe.utils.change_log import (
|
||||||
|
check_release_on_github,
|
||||||
|
get_remote_url,
|
||||||
|
parse_github_url,
|
||||||
|
)
|
||||||
from frappe.utils.data import (
|
from frappe.utils.data import (
|
||||||
add_to_date,
|
add_to_date,
|
||||||
add_years,
|
add_years,
|
||||||
|
|
@ -98,7 +102,8 @@ class TestFilters(FrappeTestCase):
|
||||||
def test_multiple_dict(self):
|
def test_multiple_dict(self):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
evaluate_filters(
|
evaluate_filters(
|
||||||
{"doctype": "User", "status": "Open", "name": "Test 1"}, {"status": "Open", "name": "Test 1"}
|
{"doctype": "User", "status": "Open", "name": "Test 1"},
|
||||||
|
{"status": "Open", "name": "Test 1"},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
|
|
@ -139,12 +144,14 @@ class TestFilters(FrappeTestCase):
|
||||||
def test_lt_gt(self):
|
def test_lt_gt(self):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
evaluate_filters(
|
evaluate_filters(
|
||||||
{"doctype": "User", "status": "Open", "age": 20}, {"status": "Open", "age": (">", 10)}
|
{"doctype": "User", "status": "Open", "age": 20},
|
||||||
|
{"status": "Open", "age": (">", 10)},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
evaluate_filters(
|
evaluate_filters(
|
||||||
{"doctype": "User", "status": "Open", "age": 20}, {"status": "Open", "age": (">", 30)}
|
{"doctype": "User", "status": "Open", "age": 20},
|
||||||
|
{"status": "Open", "age": (">", 30)},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -152,12 +159,14 @@ class TestFilters(FrappeTestCase):
|
||||||
# date fields
|
# date fields
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
evaluate_filters(
|
evaluate_filters(
|
||||||
{"doctype": "User", "birth_date": "2023-02-28"}, [("User", "birth_date", ">", "01-04-2022")]
|
{"doctype": "User", "birth_date": "2023-02-28"},
|
||||||
|
[("User", "birth_date", ">", "01-04-2022")],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
evaluate_filters(
|
evaluate_filters(
|
||||||
{"doctype": "User", "birth_date": "2023-02-28"}, [("User", "birth_date", "<", "28-02-2023")]
|
{"doctype": "User", "birth_date": "2023-02-28"},
|
||||||
|
[("User", "birth_date", "<", "28-02-2023")],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -176,7 +185,12 @@ class TestFilters(FrappeTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_like_not_like(self):
|
def test_like_not_like(self):
|
||||||
doc = {"doctype": "User", "username": "test_abc", "prefix": "startswith", "suffix": "endswith"}
|
doc = {
|
||||||
|
"doctype": "User",
|
||||||
|
"username": "test_abc",
|
||||||
|
"prefix": "startswith",
|
||||||
|
"suffix": "endswith",
|
||||||
|
}
|
||||||
|
|
||||||
test_cases = [
|
test_cases = [
|
||||||
([["username", "like", "test"]], True),
|
([["username", "like", "test"]], True),
|
||||||
|
|
@ -371,7 +385,11 @@ class TestValidationUtils(FrappeTestCase):
|
||||||
self.assertTrue(validate_url("ftp://frappe.cloud", valid_schemes=["https", "ftp"]))
|
self.assertTrue(validate_url("ftp://frappe.cloud", valid_schemes=["https", "ftp"]))
|
||||||
self.assertFalse(validate_url("bolo://frappe.io", valid_schemes=("http", "https", "ftp", "ftps")))
|
self.assertFalse(validate_url("bolo://frappe.io", valid_schemes=("http", "https", "ftp", "ftps")))
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
frappe.ValidationError, validate_url, "gopher://frappe.io", valid_schemes="https", throw=True
|
frappe.ValidationError,
|
||||||
|
validate_url,
|
||||||
|
"gopher://frappe.io",
|
||||||
|
valid_schemes="https",
|
||||||
|
throw=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_valid_email(self):
|
def test_valid_email(self):
|
||||||
|
|
@ -391,7 +409,12 @@ class TestValidationUtils(FrappeTestCase):
|
||||||
self.assertFalse(validate_email_address("test@example.com test2@example.com,undisclosed-recipient"))
|
self.assertFalse(validate_email_address("test@example.com test2@example.com,undisclosed-recipient"))
|
||||||
|
|
||||||
# Invalid with throw
|
# Invalid with throw
|
||||||
self.assertRaises(frappe.InvalidEmailAddressError, validate_email_address, "someone.com", throw=True)
|
self.assertRaises(
|
||||||
|
frappe.InvalidEmailAddressError,
|
||||||
|
validate_email_address,
|
||||||
|
"someone.com",
|
||||||
|
throw=True,
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(validate_email_address("Some%20One@frappe.com"), "Some%20One@frappe.com")
|
self.assertEqual(validate_email_address("Some%20One@frappe.com"), "Some%20One@frappe.com")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
@ -425,7 +448,8 @@ class TestImage(FrappeTestCase):
|
||||||
def test_strip_exif_data(self):
|
def test_strip_exif_data(self):
|
||||||
original_image = Image.open(frappe.get_app_path("frappe", "tests", "data", "exif_sample_image.jpg"))
|
original_image = Image.open(frappe.get_app_path("frappe", "tests", "data", "exif_sample_image.jpg"))
|
||||||
original_image_content = open(
|
original_image_content = open(
|
||||||
frappe.get_app_path("frappe", "tests", "data", "exif_sample_image.jpg"), mode="rb"
|
frappe.get_app_path("frappe", "tests", "data", "exif_sample_image.jpg"),
|
||||||
|
mode="rb",
|
||||||
).read()
|
).read()
|
||||||
|
|
||||||
new_image_content = strip_exif_data(original_image_content, "image/jpeg")
|
new_image_content = strip_exif_data(original_image_content, "image/jpeg")
|
||||||
|
|
@ -522,19 +546,33 @@ class TestDateUtils(FrappeTestCase):
|
||||||
# Monday as start of the week
|
# Monday as start of the week
|
||||||
with patch.object(frappe.utils.data, "get_first_day_of_the_week", return_value="Monday"):
|
with patch.object(frappe.utils.data, "get_first_day_of_the_week", return_value="Monday"):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
frappe.utils.get_first_day_of_week("2020-12-25"), frappe.utils.getdate("2020-12-21")
|
frappe.utils.get_first_day_of_week("2020-12-25"),
|
||||||
|
frappe.utils.getdate("2020-12-21"),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
frappe.utils.get_first_day_of_week("2020-12-20"), frappe.utils.getdate("2020-12-14")
|
frappe.utils.get_first_day_of_week("2020-12-20"),
|
||||||
|
frappe.utils.getdate("2020-12-14"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sunday as start of the week
|
# Sunday as start of the week
|
||||||
self.assertEqual(frappe.utils.get_first_day_of_week("2020-12-25"), frappe.utils.getdate("2020-12-20"))
|
self.assertEqual(
|
||||||
self.assertEqual(frappe.utils.get_first_day_of_week("2020-12-21"), frappe.utils.getdate("2020-12-20"))
|
frappe.utils.get_first_day_of_week("2020-12-25"),
|
||||||
|
frappe.utils.getdate("2020-12-20"),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
frappe.utils.get_first_day_of_week("2020-12-21"),
|
||||||
|
frappe.utils.getdate("2020-12-20"),
|
||||||
|
)
|
||||||
|
|
||||||
def test_last_day_of_week(self):
|
def test_last_day_of_week(self):
|
||||||
self.assertEqual(frappe.utils.get_last_day_of_week("2020-12-24"), frappe.utils.getdate("2020-12-26"))
|
self.assertEqual(
|
||||||
self.assertEqual(frappe.utils.get_last_day_of_week("2020-12-28"), frappe.utils.getdate("2021-01-02"))
|
frappe.utils.get_last_day_of_week("2020-12-24"),
|
||||||
|
frappe.utils.getdate("2020-12-26"),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
frappe.utils.get_last_day_of_week("2020-12-28"),
|
||||||
|
frappe.utils.getdate("2021-01-02"),
|
||||||
|
)
|
||||||
|
|
||||||
def test_is_last_day_of_the_month(self):
|
def test_is_last_day_of_the_month(self):
|
||||||
self.assertEqual(frappe.utils.is_last_day_of_the_month("2020-12-24"), False)
|
self.assertEqual(frappe.utils.is_last_day_of_the_month("2020-12-24"), False)
|
||||||
|
|
@ -674,7 +712,14 @@ class TestResponse(FrappeTestCase):
|
||||||
"time_types": [
|
"time_types": [
|
||||||
date(year=2020, month=12, day=2),
|
date(year=2020, month=12, day=2),
|
||||||
datetime(
|
datetime(
|
||||||
year=2020, month=12, day=2, hour=23, minute=23, second=23, microsecond=23, tzinfo=pytz.utc
|
year=2020,
|
||||||
|
month=12,
|
||||||
|
day=2,
|
||||||
|
hour=23,
|
||||||
|
minute=23,
|
||||||
|
second=23,
|
||||||
|
microsecond=23,
|
||||||
|
tzinfo=pytz.utc,
|
||||||
),
|
),
|
||||||
time(hour=23, minute=23, second=23, microsecond=23, tzinfo=pytz.utc),
|
time(hour=23, minute=23, second=23, microsecond=23, tzinfo=pytz.utc),
|
||||||
timedelta(days=10, hours=12, minutes=120, seconds=10),
|
timedelta(days=10, hours=12, minutes=120, seconds=10),
|
||||||
|
|
@ -699,7 +744,7 @@ class TestResponse(FrappeTestCase):
|
||||||
|
|
||||||
self.assertTrue(all([isinstance(x, str) for x in processed_object["time_types"]]))
|
self.assertTrue(all([isinstance(x, str) for x in processed_object["time_types"]]))
|
||||||
self.assertTrue(all([isinstance(x, float) for x in processed_object["float"]]))
|
self.assertTrue(all([isinstance(x, float) for x in processed_object["float"]]))
|
||||||
self.assertTrue(all([isinstance(x, (list, str)) for x in processed_object["iter"]]))
|
self.assertTrue(all([isinstance(x, list | str) for x in processed_object["iter"]]))
|
||||||
self.assertIsInstance(processed_object["string"], str)
|
self.assertIsInstance(processed_object["string"], str)
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
json.dumps(BAD_OBJECT, default=json_handler)
|
json.dumps(BAD_OBJECT, default=json_handler)
|
||||||
|
|
@ -711,13 +756,17 @@ class TestTimeDeltaUtils(FrappeTestCase):
|
||||||
self.assertEqual(format_timedelta(timedelta(hours=10)), "10:00:00")
|
self.assertEqual(format_timedelta(timedelta(hours=10)), "10:00:00")
|
||||||
self.assertEqual(format_timedelta(timedelta(hours=100)), "100:00:00")
|
self.assertEqual(format_timedelta(timedelta(hours=100)), "100:00:00")
|
||||||
self.assertEqual(format_timedelta(timedelta(seconds=100, microseconds=129)), "0:01:40.000129")
|
self.assertEqual(format_timedelta(timedelta(seconds=100, microseconds=129)), "0:01:40.000129")
|
||||||
self.assertEqual(format_timedelta(timedelta(seconds=100, microseconds=12212199129)), "3:25:12.199129")
|
self.assertEqual(
|
||||||
|
format_timedelta(timedelta(seconds=100, microseconds=12212199129)),
|
||||||
|
"3:25:12.199129",
|
||||||
|
)
|
||||||
|
|
||||||
def test_parse_timedelta(self):
|
def test_parse_timedelta(self):
|
||||||
self.assertEqual(parse_timedelta("0:0:0"), timedelta(seconds=0))
|
self.assertEqual(parse_timedelta("0:0:0"), timedelta(seconds=0))
|
||||||
self.assertEqual(parse_timedelta("10:0:0"), timedelta(hours=10))
|
self.assertEqual(parse_timedelta("10:0:0"), timedelta(hours=10))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
parse_timedelta("7 days, 0:32:18.192221"), timedelta(days=7, seconds=1938, microseconds=192221)
|
parse_timedelta("7 days, 0:32:18.192221"),
|
||||||
|
timedelta(days=7, seconds=1938, microseconds=192221),
|
||||||
)
|
)
|
||||||
self.assertEqual(parse_timedelta("7 days, 0:32:18"), timedelta(days=7, seconds=1938))
|
self.assertEqual(parse_timedelta("7 days, 0:32:18"), timedelta(days=7, seconds=1938))
|
||||||
|
|
||||||
|
|
@ -982,7 +1031,7 @@ class TestMiscUtils(FrappeTestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestTypingValidations(FrappeTestCase):
|
class TestTypingValidations(FrappeTestCase):
|
||||||
ERR_REGEX = f"^Argument '.*' should be of type '.*' but got '.*' instead.$"
|
ERR_REGEX = "^Argument '.*' should be of type '.*' but got '.*' instead.$"
|
||||||
|
|
||||||
def test_validate_whitelisted_api(self):
|
def test_validate_whitelisted_api(self):
|
||||||
from inspect import signature
|
from inspect import signature
|
||||||
|
|
@ -1009,9 +1058,9 @@ class TestTypingValidations(FrappeTestCase):
|
||||||
class TestTBSanitization(FrappeTestCase):
|
class TestTBSanitization(FrappeTestCase):
|
||||||
def test_traceback_sanitzation(self):
|
def test_traceback_sanitzation(self):
|
||||||
try:
|
try:
|
||||||
password = "42"
|
password = "42" # noqa: F841
|
||||||
args = {"password": "42", "pwd": "42", "safe": "safe_value"}
|
args = {"password": "42", "pwd": "42", "safe": "safe_value"} # noqa: F841
|
||||||
args = frappe._dict({"password": "42", "pwd": "42", "safe": "safe_value"})
|
args = frappe._dict({"password": "42", "pwd": "42", "safe": "safe_value"}) # noqa: F841
|
||||||
raise Exception
|
raise Exception
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback = frappe.get_traceback(with_context=True)
|
traceback = frappe.get_traceback(with_context=True)
|
||||||
|
|
@ -1100,7 +1149,10 @@ class TestRounding(FrappeTestCase):
|
||||||
self.assertEqual(flt(3.35, 1, rounding_method=rounding_method), 3.4)
|
self.assertEqual(flt(3.35, 1, rounding_method=rounding_method), 3.4)
|
||||||
|
|
||||||
@change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
|
@change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
|
||||||
@given(st.decimals(min_value=-1e8, max_value=1e8), st.integers(min_value=-2, max_value=4))
|
@given(
|
||||||
|
st.decimals(min_value=-1e8, max_value=1e8),
|
||||||
|
st.integers(min_value=-2, max_value=4),
|
||||||
|
)
|
||||||
def test_normal_rounding_property(self, number, precision):
|
def test_normal_rounding_property(self, number, precision):
|
||||||
with localcontext() as ctx:
|
with localcontext() as ctx:
|
||||||
ctx.rounding = ROUND_HALF_UP
|
ctx.rounding = ROUND_HALF_UP
|
||||||
|
|
@ -1165,7 +1217,10 @@ class TestRounding(FrappeTestCase):
|
||||||
self.assertEqual(flt(-3.35, 1, rounding_method=rounding_method), -3.4)
|
self.assertEqual(flt(-3.35, 1, rounding_method=rounding_method), -3.4)
|
||||||
|
|
||||||
@change_settings("System Settings", {"rounding_method": "Banker's Rounding"})
|
@change_settings("System Settings", {"rounding_method": "Banker's Rounding"})
|
||||||
@given(st.decimals(min_value=-1e8, max_value=1e8), st.integers(min_value=-2, max_value=4))
|
@given(
|
||||||
|
st.decimals(min_value=-1e8, max_value=1e8),
|
||||||
|
st.integers(min_value=-2, max_value=4),
|
||||||
|
)
|
||||||
def test_bankers_rounding_property(self, number, precision):
|
def test_bankers_rounding_property(self, number, precision):
|
||||||
self.assertEqual(Decimal(str(flt(float(number), precision))), round(number, precision))
|
self.assertEqual(Decimal(str(flt(float(number), precision))), round(number, precision))
|
||||||
|
|
||||||
|
|
@ -1173,10 +1228,13 @@ class TestRounding(FrappeTestCase):
|
||||||
self.assertEqual(frappe.get_system_settings("rounding_method"), "Banker's Rounding")
|
self.assertEqual(frappe.get_system_settings("rounding_method"), "Banker's Rounding")
|
||||||
|
|
||||||
|
|
||||||
class TestTypingValidations(FrappeTestCase):
|
class TestArgumentTypingValidations(FrappeTestCase):
|
||||||
def test_validate_argument_types(self):
|
def test_validate_argument_types(self):
|
||||||
from frappe.core.doctype.doctype.doctype import DocType
|
from frappe.core.doctype.doctype.doctype import DocType
|
||||||
from frappe.utils.typing_validations import FrappeTypeError, validate_argument_types
|
from frappe.utils.typing_validations import (
|
||||||
|
FrappeTypeError,
|
||||||
|
validate_argument_types,
|
||||||
|
)
|
||||||
|
|
||||||
@validate_argument_types
|
@validate_argument_types
|
||||||
def test_simple_types(a: int, b: float, c: bool):
|
def test_simple_types(a: int, b: float, c: bool):
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class FrappeTestCase(unittest.TestCase):
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
actual_child_docs = actual.get(field)
|
actual_child_docs = actual.get(field)
|
||||||
self.assertEqual(len(value), len(actual_child_docs), msg=f"{field} length should be same")
|
self.assertEqual(len(value), len(actual_child_docs), msg=f"{field} length should be same")
|
||||||
for exp_child, actual_child in zip(value, actual_child_docs):
|
for exp_child, actual_child in zip(value, actual_child_docs, strict=False):
|
||||||
self.assertDocumentEqual(exp_child, actual_child)
|
self.assertDocumentEqual(exp_child, actual_child)
|
||||||
else:
|
else:
|
||||||
self._compare_field(value, actual.get(field), actual, field)
|
self._compare_field(value, actual.get(field), actual, field)
|
||||||
|
|
@ -73,7 +73,7 @@ class FrappeTestCase(unittest.TestCase):
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(
|
||||||
expected, actual, places=precision, msg=f"{field} should be same to {precision} digits"
|
expected, actual, places=precision, msg=f"{field} should be same to {precision} digits"
|
||||||
)
|
)
|
||||||
elif isinstance(expected, (bool, int)):
|
elif isinstance(expected, bool | int):
|
||||||
self.assertEqual(expected, cint(actual), msg=msg)
|
self.assertEqual(expected, cint(actual), msg=msg)
|
||||||
elif isinstance(expected, datetime_like_types):
|
elif isinstance(expected, datetime_like_types):
|
||||||
self.assertEqual(str(expected), str(actual), msg=msg)
|
self.assertEqual(str(expected), str(actual), msg=msg)
|
||||||
|
|
|
||||||
|
|
@ -204,9 +204,7 @@ def get_translation_dict_from_file(path, lang, app, throw=False) -> dict[str, st
|
||||||
elif len(item) in [2, 3]:
|
elif len(item) in [2, 3]:
|
||||||
translation_map[item[0]] = strip(item[1])
|
translation_map[item[0]] = strip(item[1])
|
||||||
elif item:
|
elif item:
|
||||||
msg = "Bad translation in '{app}' for language '{lang}': {values}".format(
|
msg = f"Bad translation in '{app}' for language '{lang}': {cstr(item)}"
|
||||||
app=app, lang=lang, values=cstr(item)
|
|
||||||
)
|
|
||||||
frappe.log_error(message=msg, title="Error in translation file")
|
frappe.log_error(message=msg, title="Error in translation file")
|
||||||
if throw:
|
if throw:
|
||||||
frappe.throw(msg, title="Error in translation file")
|
frappe.throw(msg, title="Error in translation file")
|
||||||
|
|
@ -545,7 +543,7 @@ def get_all_messages_from_js_files(app_name=None):
|
||||||
messages = []
|
messages = []
|
||||||
for app in [app_name] if app_name else frappe.get_installed_apps(_ensure_on_bench=True):
|
for app in [app_name] if app_name else frappe.get_installed_apps(_ensure_on_bench=True):
|
||||||
if os.path.exists(frappe.get_app_path(app, "public")):
|
if os.path.exists(frappe.get_app_path(app, "public")):
|
||||||
for basepath, folders, files in os.walk(frappe.get_app_path(app, "public")):
|
for basepath, folders, files in os.walk(frappe.get_app_path(app, "public")): # noqa: B007
|
||||||
if "frappe/public/js/lib" in basepath:
|
if "frappe/public/js/lib" in basepath:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -761,6 +759,7 @@ def update_translations(lang, untranslated_file, translated_file, app="_ALL_APPS
|
||||||
for key, value in zip(
|
for key, value in zip(
|
||||||
frappe.get_file_items(untranslated_file, ignore_empty_lines=False),
|
frappe.get_file_items(untranslated_file, ignore_empty_lines=False),
|
||||||
frappe.get_file_items(translated_file, ignore_empty_lines=False),
|
frappe.get_file_items(translated_file, ignore_empty_lines=False),
|
||||||
|
strict=False,
|
||||||
):
|
):
|
||||||
# undo hack in get_untranslated
|
# undo hack in get_untranslated
|
||||||
translation_dict[restore_newlines(key)] = restore_newlines(value)
|
translation_dict[restore_newlines(key)] = restore_newlines(value)
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue