Merge branch 'develop' into po-translation

This commit is contained in:
Ankush Menat 2024-01-10 16:17:47 +05:30
commit 9620a3c596
175 changed files with 1692 additions and 1154 deletions

View file

@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
version: ["13", "14", "15"]
version: ["14", "15"]
steps:
- uses: octokit/request-action@v2.x

View file

@ -11,7 +11,6 @@ concurrency:
group: server-develop-${{ github.event_name }}-${{ github.event.number }}
cancel-in-progress: true
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
contents: read
@ -48,8 +47,8 @@ jobs:
strategy:
fail-fast: false
matrix:
db: ["mariadb", "postgres"]
container: [1, 2]
db: ["mariadb", "postgres"]
container: [1, 2]
services:
mariadb:
@ -85,7 +84,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: "3.12"
- name: Check for valid Python & Merge Conflicts
run: |
@ -149,7 +148,14 @@ jobs:
- name: Show bench output
if: ${{ always() }}
run: cat ~/frappe-bench/bench_start.log || true
run: |
cd ~/frappe-bench
cat bench_start.log || true
cd logs
for f in ./*.log*; do
echo "Printing log: $f";
cat $f
done
- name: Upload coverage data
uses: actions/upload-artifact@v3
@ -166,8 +172,8 @@ jobs:
strategy:
matrix:
db: ["mariadb", "postgres"]
container: [1, 2]
db: ["mariadb", "postgres"]
container: [1, 2]
steps:
- name: Pass skipped tests unconditionally

View file

@ -46,8 +46,8 @@ jobs:
strategy:
fail-fast: false
matrix:
# Make sure you modify coverage submission file list if changing this
container: [1, 2, 3]
# Make sure you modify coverage submission file list if changing this
container: [1, 2, 3]
name: UI Tests (Cypress)
@ -67,7 +67,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: "3.12"
- name: Check for valid Python & Merge Conflicts
run: |
@ -166,7 +166,14 @@ jobs:
- name: Show bench output
if: ${{ always() }}
run: cat ~/frappe-bench/bench_start.log || true
run: |
cd ~/frappe-bench
cat bench_start.log || true
cd logs
for f in ./*.log*; do
echo "Printing log: $f";
cat $f
done
faux-test:
runs-on: ubuntu-latest
@ -175,7 +182,7 @@ jobs:
name: UI Tests (Cypress)
strategy:
matrix:
container: [1, 2, 3]
container: [1, 2, 3]
steps:
- name: Pass skipped tests unconditionally

View file

@ -7,6 +7,7 @@ context("Date Control", () => {
function get_dialog(date_field_options) {
return cy.dialog({
title: "Date",
animate: false,
fields: [
{
label: "Date",
@ -75,6 +76,8 @@ context("Date Control", () => {
//Verifying if clicking on "Today" button matches today's date
cy.window().then((win) => {
// `expect` can not wait like `should`
cy.wait(500);
expect(win.cur_dialog.fields_dict.date.value).to.be.equal(
win.frappe.datetime.get_today()
);

View file

@ -6,16 +6,17 @@ context("Navigation", () => {
});
it("Navigate to route with hash in document name", () => {
cy.insert_doc(
"ToDo",
"Client Script",
{
__newname: "ABC#123",
description: "Test this",
dt: "User",
script: "console.log('ran')",
enabled: 0,
},
true
);
cy.visit(`/app/todo/${encodeURIComponent("ABC#123")}`);
cy.title().should("eq", "Test this - ABC#123");
cy.get_field("description", "Text Editor").contains("Test this");
cy.visit(`/app/client-script/${encodeURIComponent("ABC#123")}`);
cy.title().should("eq", "ABC#123");
cy.go("back");
cy.title().should("eq", "Website");
});

View file

@ -8,6 +8,7 @@ const test_queries = [
`?date=%5B">"%2C"2022-06-01"%5D`,
`?name=%5B"like"%2C"%2542%25"%5D`,
`?status=%5B"not%20in"%2C%5B"Open"%2C"Closed"%5D%5D`,
`?status=%5B%22%21%3D%22%2C%22Closed%22%5D&status=%5B%22%21%3D%22%2C%22Cancelled%22%5D`,
];
describe("SPA Routing", { scrollBehavior: false }, () => {

View file

@ -224,8 +224,8 @@ context("View", () => {
});
});
it("Route to Settings Workspace", () => {
cy.visit("/app/settings");
cy.get(".title-text").should("contain", "Settings");
it("Route to Website Workspace", () => {
cy.visit("/app/website");
cy.get(".title-text").should("contain", "Website");
});
});

View file

@ -7,8 +7,8 @@ context("Workspace 2.0", () => {
it("Navigate to page from sidebar", () => {
cy.visit("/app/build");
cy.get(".codex-editor__redactor .ce-block");
cy.get('.sidebar-item-container[item-name="Settings"]').first().click();
cy.location("pathname").should("eq", "/app/settings");
cy.get('.sidebar-item-container[item-name="Website"]').first().click();
cy.location("pathname").should("eq", "/app/website");
});
it("Create Private Page", () => {

View file

@ -449,27 +449,8 @@ Cypress.Commands.add("click_menu_button", (name) => {
});
Cypress.Commands.add("clear_filters", () => {
let has_filter = false;
cy.intercept({
method: "POST",
url: "api/method/frappe.model.utils.user_settings.save",
}).as("filter-saved");
cy.get(".filter-section .filter-button").click({ force: true });
cy.wait(300);
cy.get(".filter-popover").should("exist");
cy.get(".filter-popover").then((popover) => {
if (popover.find("input.input-with-feedback")[0].value != "") {
has_filter = true;
}
});
cy.get(".filter-popover").find(".clear-filters").click();
cy.get(".filter-section .filter-button").click();
cy.window()
.its("cur_list")
.then((cur_list) => {
cur_list && cur_list.filter_area && cur_list.filter_area.clear();
has_filter && cy.wait("@filter-saved");
});
cy.get(".filter-x-button").click({ force: true });
cy.wait(500);
});
Cypress.Commands.add("click_modal_primary_button", (btn_name) => {

View file

@ -59,32 +59,6 @@ if _dev_server:
warnings.simplefilter("always", DeprecationWarning)
warnings.simplefilter("always", PendingDeprecationWarning)
# Always initialize sentry SDK if the DSN is sent
if sentry_dsn := os.getenv("FRAPPE_SENTRY_DSN"):
import sentry_sdk
from sentry_sdk.integrations.argv import ArgvIntegration
from sentry_sdk.integrations.atexit import AtexitIntegration
from sentry_sdk.integrations.dedupe import DedupeIntegration
from sentry_sdk.integrations.excepthook import ExcepthookIntegration
from sentry_sdk.integrations.modules import ModulesIntegration
from frappe.utils.sentry import before_send
sentry_sdk.init(
dsn=sentry_dsn,
before_send=before_send,
release=__version__,
auto_enabling_integrations=False,
default_integrations=False,
integrations=[
AtexitIntegration(),
ExcepthookIntegration(),
DedupeIntegration(),
ModulesIntegration(),
ArgvIntegration(),
],
)
class _dict(dict):
"""dict like object that exposes keys as attributes"""
@ -1001,6 +975,7 @@ def has_permission(
throw=False,
*,
parent_doctype=None,
debug=False,
):
"""
Return True if the user has permission `ptype` for given `doctype` or `doc`.
@ -1025,22 +1000,15 @@ def has_permission(
user=user,
raise_exception=throw,
parent_doctype=parent_doctype,
debug=debug,
)
if throw and not out:
# mimics frappe.throw
document_label = (
f"{_(doctype)} {doc if isinstance(doc, str) else doc.name}" if doc else _(doctype)
)
msgprint(
_("No permission for {0}").format(document_label),
raise_exception=ValidationError,
title=None,
indicator="red",
is_minimizable=None,
wide=None,
as_list=False,
)
frappe.flags.error_message = _("No permission for {0}").format(document_label)
raise frappe.PermissionError
return out
@ -1720,17 +1688,14 @@ def get_newargs(fn: Callable, kwargs: dict[str, Any]) -> dict[str, Any]:
# Ref: https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
varkw_exist = False
if hasattr(fn, "fnargs"):
fnargs = fn.fnargs
else:
signature = inspect.signature(fn)
fnargs = list(signature.parameters)
signature = inspect.signature(fn)
fnargs = list(signature.parameters)
for param_name, parameter in signature.parameters.items():
if parameter.kind == inspect.Parameter.VAR_KEYWORD:
varkw_exist = True
fnargs.remove(param_name)
break
for param_name, parameter in signature.parameters.items():
if parameter.kind == inspect.Parameter.VAR_KEYWORD:
varkw_exist = True
fnargs.remove(param_name)
break
newargs = {}
for a in kwargs:

View file

@ -140,7 +140,7 @@ def application(request: Request):
try:
run_after_request_hooks(request, response)
except Exception as e:
except Exception:
# We can not handle exceptions safely here.
frappe.logger().error("Failed to run after request hook", exc_info=True)
@ -420,6 +420,50 @@ def sync_database(rollback: bool) -> bool:
return rollback
# Always initialize sentry SDK if the DSN is sent
if sentry_dsn := os.getenv("FRAPPE_SENTRY_DSN"):
import sentry_sdk
from sentry_sdk.integrations.argv import ArgvIntegration
from sentry_sdk.integrations.atexit import AtexitIntegration
from sentry_sdk.integrations.dedupe import DedupeIntegration
from sentry_sdk.integrations.excepthook import ExcepthookIntegration
from sentry_sdk.integrations.modules import ModulesIntegration
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
from frappe.utils.sentry import FrappeIntegration, before_send
integrations = [
AtexitIntegration(),
ExcepthookIntegration(),
DedupeIntegration(),
ModulesIntegration(),
ArgvIntegration(),
]
experiments = {}
kwargs = {}
if os.getenv("ENABLE_SENTRY_DB_MONITORING"):
integrations.append(FrappeIntegration())
experiments["record_sql_params"] = True
if tracing_sample_rate := os.getenv("SENTRY_TRACING_SAMPLE_RATE"):
kwargs["traces_sample_rate"] = float(tracing_sample_rate)
application = SentryWsgiMiddleware(application)
sentry_sdk.init(
dsn=sentry_dsn,
before_send=before_send,
attach_stacktrace=True,
release=frappe.__version__,
auto_enabling_integrations=False,
default_integrations=False,
integrations=integrations,
_experiments=experiments,
**kwargs,
)
def serve(
port=8000,
profile=False,

View file

@ -1,6 +1,6 @@
{
"charts": [],
"content": "[{\"id\":\"-P-RG1wVHg\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"sR-UFcO7II\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Import Data\",\"col\":3}},{\"id\":\"IkcVmgWb3z\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"ToDo\",\"col\":3}},{\"id\":\"6wir-jZFRE\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"File\",\"col\":3}},{\"id\":\"45a1jzQkTm\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Assignment Rule\",\"col\":3}},{\"id\":\"LdZrgvxxo7\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yNSSTIaDWZ\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Documents</b></span>\",\"col\":12}},{\"id\":\"0yceBIfhHM\",\"type\":\"card\",\"data\":{\"card_name\":\"Data\",\"col\":4}},{\"id\":\"42WbBA9rpj\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"7_U7_xCOos\",\"type\":\"card\",\"data\":{\"card_name\":\"Email\",\"col\":4}},{\"id\":\"SlYKJZj5r3\",\"type\":\"card\",\"data\":{\"card_name\":\"Automation\",\"col\":4}}]",
"content": "[{\"id\":\"-P-RG1wVHg\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"sR-UFcO7II\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Import Data\",\"col\":3}},{\"id\":\"IkcVmgWb3z\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"ToDo\",\"col\":3}},{\"id\":\"6wir-jZFRE\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"File\",\"col\":3}},{\"id\":\"45a1jzQkTm\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Assignment Rule\",\"col\":3}},{\"id\":\"LdZrgvxxo7\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yNSSTIaDWZ\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Documents</b></span>\",\"col\":12}},{\"id\":\"0yceBIfhHM\",\"type\":\"card\",\"data\":{\"card_name\":\"Data\",\"col\":4}},{\"id\":\"42WbBA9rpj\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"wE9n7TIrAc\",\"type\":\"card\",\"data\":{\"card_name\":\"Alerts and Notifications\",\"col\":4}},{\"id\":\"7_U7_xCOos\",\"type\":\"card\",\"data\":{\"card_name\":\"Email\",\"col\":4}},{\"id\":\"3imoh2oqsJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"id\":\"SlYKJZj5r3\",\"type\":\"card\",\"data\":{\"card_name\":\"Automation\",\"col\":4}},{\"id\":\"O7jrc2YQTN\",\"type\":\"card\",\"data\":{\"card_name\":\"Newsletter\",\"col\":4}}]",
"creation": "2020-03-02 14:53:24.980279",
"custom_blocks": [],
"docstatus": 0,
@ -12,36 +12,6 @@
"is_hidden": 0,
"label": "Tools",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Email",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Newsletter",
"link_count": 0,
"link_to": "Newsletter",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Group",
"link_count": 0,
"link_to": "Email Group",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -192,9 +162,165 @@
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Email",
"link_count": 3,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Email Account",
"link_count": 0,
"link_to": "Email Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Email Domain",
"link_count": 0,
"link_to": "Email Domain",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Email Template",
"link_count": 0,
"link_to": "Email Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Newsletter",
"link_count": 2,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Newsletter",
"link_count": 0,
"link_to": "Newsletter",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Email Group",
"link_count": 0,
"link_to": "Email Group",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Printing",
"link_count": 4,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Print Format Builder",
"link_count": 0,
"link_to": "print-format-builder",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Print Format Builder (New)",
"link_count": 0,
"link_to": "print-format-builder-beta",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Print Settings",
"link_count": 0,
"link_to": "Print Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Print Heading",
"link_count": 0,
"link_to": "Print Heading",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Alerts and Notifications",
"link_count": 3,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Notification",
"link_count": 0,
"link_to": "Notification",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Auto Email Report",
"link_count": 0,
"link_to": "Auto Email Report",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Notification Settings",
"link_count": 0,
"link_to": "Notification Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2023-05-24 14:47:24.740856",
"modified": "2024-01-02 15:47:12.939478",
"modified_by": "Administrator",
"module": "Automation",
"name": "Tools",

View file

@ -270,9 +270,6 @@ def get_user_info():
user_info = frappe._dict()
add_user_info(frappe.session.user, user_info)
if frappe.session.user == "Administrator" and user_info.Administrator.email:
user_info[user_info.Administrator.email] = user_info.Administrator
return user_info

View file

@ -260,8 +260,28 @@ def restore_backup(
admin_password,
force,
):
from pathlib import Path
from frappe.installer import _new_site, is_downgrade, is_partial, validate_database_sql
# Check for the backup file in the backup directory, as well as the main bench directory
dirs = (f"{site}/private/backups", "..")
# Try to resolve path to the file if we can't find it directly
if not Path(sql_file_path).exists():
click.secho(
f"File {sql_file_path} not found. Trying to check in alternative directories.", fg="yellow"
)
for dir in dirs:
potential_path = Path(dir) / Path(sql_file_path)
if potential_path.exists():
sql_file_path = str(potential_path.resolve())
click.secho(f"File {sql_file_path} found.", fg="green")
break
else:
click.secho(f"File {sql_file_path} not found.", fg="red")
sys.exit(1)
if is_partial(sql_file_path):
click.secho(
"Partial Backup file detected. You cannot use a partial file to restore a Frappe site.",
@ -448,7 +468,7 @@ def install_app(context, apps, force=False):
print(f"App {app} is Incompatible with Site {site}{err_msg}")
exit_code = 1
except Exception as err:
err_msg = f": {str(err)}\n{frappe.get_traceback()}"
err_msg = f": {str(err)}\n{frappe.get_traceback(with_context=True)}"
print(f"An error occurred while installing {app}{err_msg}")
exit_code = 1
@ -570,7 +590,7 @@ def describe_database_table(context, doctype, column):
def _extract_table_stats(doctype: str, columns: list[str]) -> dict:
from frappe.utils import cstr, get_table_name
from frappe.utils import cint, cstr, get_table_name
def sql_bool(val):
return cstr(val).lower() in ("yes", "1", "true")
@ -610,7 +630,13 @@ def _extract_table_stats(doctype: str, columns: list[str]) -> dict:
if idx["Seq_in_index"] == 1:
update_cardinality(idx["Column_name"], idx["Cardinality"])
total_rows = frappe.db.count(doctype)
total_rows = cint(
frappe.db.sql(
f"""select table_rows
from information_schema.tables
where table_name = 'tab{doctype}'"""
)[0][0]
)
# fetch accurate cardinality for columns by query. WARN: This can take a lot of time.
for column in columns:
@ -893,7 +919,7 @@ def backup(
fg="red",
)
if verbose:
print(frappe.get_traceback())
print(frappe.get_traceback(with_context=True))
exit_code = 1
continue
if frappe.get_system_settings("encrypt_backup") and frappe.get_site_config().encryption_key:

View file

@ -267,6 +267,7 @@ frappe.ui.form.on("Communication", {
$.extend(args, {
subject: __("Re: {0}", [frm.doc.subject]),
recipients: frm.doc.sender,
is_a_reply: true,
});
new frappe.views.CommunicationComposer(args);
@ -278,6 +279,7 @@ frappe.ui.form.on("Communication", {
subject: __("Res: {0}", [frm.doc.subject]),
recipients: frm.doc.sender,
cc: frm.doc.cc,
is_a_reply: true,
});
new frappe.views.CommunicationComposer(args);
},
@ -287,6 +289,7 @@ frappe.ui.form.on("Communication", {
$.extend(args, {
forward: true,
subject: __("Fw: {0}", [frm.doc.subject]),
is_a_reply: true,
});
new frappe.views.CommunicationComposer(args);

View file

@ -306,7 +306,7 @@ class Communication(Document, CommunicationEmailMixin):
emails = split_emails(emails) if isinstance(emails, str) else (emails or [])
if exclude_displayname:
return [email.lower() for email in {parse_addr(email)[1] for email in emails} if email]
return [email.lower() for email in set(emails) if email]
return [email for email in set(emails) if email]
def to_list(self, exclude_displayname=True):
"""Return `to` list."""
@ -501,14 +501,15 @@ def on_doctype_update():
frappe.db.add_index("Communication", ["message_id(140)"])
def has_permission(doc, ptype, user):
def has_permission(doc, ptype, user=None, debug=False):
if ptype == "read":
if doc.reference_doctype == "Communication" and doc.reference_name == doc.name:
return
if doc.reference_doctype and doc.reference_name:
if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name):
return True
return frappe.has_permission(
doc.reference_doctype, ptype="read", doc=doc.reference_name, user=user, debug=debug
)
def get_permission_query_conditions_for_communication(user):

View file

@ -145,6 +145,12 @@ const get_doctypes = (parentdt) => {
const add_doctype_field_multicheck_control = (doctype, parent_wrapper) => {
const fields = get_fields(doctype);
frappe.model.std_fields
.filter((df) => ["owner", "creation"].includes(df.fieldname))
.forEach((df) => {
fields.push(df);
});
const options = fields.map((df) => {
return {
label: df.label,

View file

@ -212,8 +212,23 @@ class DataExporter:
# build list of valid docfields
tablecolumns = []
table_name = "tab" + dt
for f in frappe.db.get_table_columns_description(table_name):
field = meta.get_field(f.name)
if f.name in ["owner", "creation"]:
std_field = next((x for x in frappe.model.std_fields if x["fieldname"] == f.name), None)
if std_field:
field = frappe._dict(
{
"fieldname": std_field.get("fieldname"),
"label": std_field.get("label"),
"fieldtype": std_field.get("fieldtype"),
"options": std_field.get("options"),
"idx": 0,
"parent": dt,
}
)
if field and (
(self.select_columns and f.name in self.select_columns[dt]) or not self.select_columns
):
@ -404,7 +419,6 @@ class DataExporter:
)
for ci, child in enumerate(data_row.run(as_dict=True)):
self.add_data_row(rows, c["doctype"], c["parentfield"], child, ci)
for row in rows:
self.writer.writerow(row)

View file

@ -88,8 +88,8 @@ class TestDataExporter(FrappeTestCase):
self.assertEqual(frappe.response["type"], "csv")
self.assertEqual(frappe.response["doctype"], self.doctype_name)
self.assertTrue(frappe.response["result"])
self.assertIn('Child Title 1",50', frappe.response["result"])
self.assertIn('Child Title 2",51', frappe.response["result"])
self.assertRegex(frappe.response["result"], r"Child Title 1.*?,50")
self.assertRegex(frappe.response["result"], r"Child Title 2.*?,51")
def test_export_type(self):
for type in ["csv", "Excel"]:

View file

@ -449,7 +449,6 @@ frappe.ui.form.on("Data Import", {
}
} else {
let messages = JSON.parse(log.messages || "[]")
.map(JSON.parse)
.map((m) => {
let title = m.title ? `<strong>${m.title}</strong>` : "";
let message = m.message ? `<div>${m.message}</div>` : "";
@ -507,7 +506,13 @@ frappe.ui.form.on("Data Import", {
},
show_import_log(frm) {
if (!frm.doc.show_failed_logs) {
frm.toggle_display("import_log_preview", false);
return;
}
frm.toggle_display("import_log_section", false);
frm.toggle_display("import_log_preview", true);
if (frm.import_in_progress) {
return;

View file

@ -241,9 +241,11 @@ def import_file(doctype, file_path, import_type, submit_after_import=False, cons
i.import_data()
def import_doc(path, pre_process=None):
def import_doc(path, pre_process=None, sort=False):
if os.path.isdir(path):
files = [os.path.join(path, f) for f in os.listdir(path)]
if sort:
files.sort()
else:
files = [path]

View file

@ -234,6 +234,7 @@ class DocType(Document):
"DocPerm",
"Custom Field",
"Customize Form Field",
"Web Form Field",
"DocField",
]
@ -593,7 +594,7 @@ class DocType(Document):
if not self.has_value_changed("has_web_view"):
return
despaced_name = self.name.replace(" ", "_")
despaced_name = self.name.replace(" ", "")
scrubbed_name = frappe.scrub(self.name)
scrubbed_module = frappe.scrub(self.module)
controller_path = frappe.get_module_path(

View file

@ -778,11 +778,11 @@ def on_doctype_update():
frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"])
def has_permission(doc, ptype=None, user=None):
def has_permission(doc, ptype=None, user=None, debug=False):
user = user or frappe.session.user
if ptype == "create":
return frappe.has_permission("File", "create", user=user)
return frappe.has_permission("File", "create", user=user, debug=debug)
if not doc.is_private or (user != "Guest" and doc.owner == user) or user == "Administrator":
return True
@ -798,9 +798,9 @@ def has_permission(doc, ptype=None, user=None):
return False
if ptype in ["write", "create", "delete"]:
return ref_doc.has_permission("write")
return ref_doc.has_permission("write", debug=debug, user=user)
else:
return ref_doc.has_permission("read")
return ref_doc.has_permission("read", debug=debug, user=user)
return False

View file

@ -16,10 +16,6 @@ class TestPage(FrappeTestCase):
frappe.NameError,
frappe.get_doc(dict(doctype="Page", page_name="DocType", module="Core")).insert,
)
self.assertRaises(
frappe.NameError,
frappe.get_doc(dict(doctype="Page", page_name="Settings", module="Core")).insert,
)
@unittest.skipUnless(
os.access(frappe.get_app_path("frappe"), os.W_OK), "Only run if frappe app paths is writable"

View file

@ -0,0 +1,24 @@
// Copyright (c) 2024, Frappe Technologies and contributors
// For license information, please see license.txt
const call_debug = (frm) => {
frm.trigger("debug");
};
frappe.ui.form.on("Permission Debugger", {
refresh(frm) {
frm.disable_save();
},
docname: call_debug,
ref_doctype(frm) {
frm.doc.docname = ""; // Usually doctype change invalidates docname
call_debug(frm);
},
user: call_debug,
permission_type: call_debug,
debug(frm) {
if (frm.doc.ref_doctype && frm.doc.user) {
frm.call("debug");
}
},
});

View file

@ -0,0 +1,90 @@
{
"actions": [],
"allow_rename": 1,
"beta": 1,
"creation": "2024-01-03 17:43:27.257317",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"ref_doctype",
"column_break_mcqo",
"docname",
"column_break_xbrd",
"user",
"column_break_nvaa",
"permission_type",
"section_break_hkjp",
"output"
],
"fields": [
{
"fieldname": "ref_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"label": "DocType",
"options": "DocType",
"reqd": 1
},
{
"fieldname": "docname",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Document",
"options": "ref_doctype"
},
{
"fieldname": "column_break_mcqo",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_xbrd",
"fieldtype": "Column Break"
},
{
"fieldname": "user",
"fieldtype": "Link",
"label": "User",
"options": "User",
"reqd": 1
},
{
"fieldname": "section_break_hkjp",
"fieldtype": "Section Break"
},
{
"fieldname": "output",
"fieldtype": "Code",
"label": "Output",
"read_only": 1
},
{
"fieldname": "column_break_nvaa",
"fieldtype": "Column Break"
},
{
"fieldname": "permission_type",
"fieldtype": "Select",
"label": "Permission Type",
"options": "read\nwrite\ncreate\ndelete\nsubmit\ncancel\nselect\namend\nprint\nemail\nreport\nimport\nexport\nshare"
}
],
"index_web_pages_for_search": 1,
"is_virtual": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-10 14:17:49.722593",
"modified_by": "Administrator",
"module": "Core",
"name": "Permission Debugger",
"owner": "Administrator",
"permissions": [
{
"read": 1,
"role": "System Manager",
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View file

@ -0,0 +1,75 @@
# Copyright (c) 2024, Frappe Technologies and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from frappe.permissions import _pop_debug_log, has_permission
class PermissionDebugger(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
docname: DF.DynamicLink | None
output: DF.Code | None
permission_type: DF.Literal[
"read",
"write",
"create",
"delete",
"submit",
"cancel",
"select",
"amend",
"print",
"email",
"report",
"import",
"export",
"share",
]
ref_doctype: DF.Link
user: DF.Link
# end: auto-generated types
@frappe.whitelist()
def debug(self):
if not (self.ref_doctype and self.user):
return
result = has_permission(
self.ref_doctype, ptype=self.permission_type, doc=self.docname, user=self.user, debug=True
)
self.output = "\n==============================\n".join(_pop_debug_log())
self.output += "\n\n" + f"Ouput of has_permission: {result}"
# None of these apply, overriden for sanity.
def load_from_db(self):
super(Document, self).__init__({"modified": None, "permission_type": "read"})
def db_insert(self, *args, **kwargs):
...
def db_update(self):
...
@staticmethod
def get_list(args):
...
@staticmethod
def get_count(args):
...
@staticmethod
def get_stats(args):
...
def delete(self):
...

View file

@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe Technologies and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestPermissionDebugger(FrappeTestCase):
pass

View file

@ -113,7 +113,7 @@ def generate_report(prepared_report):
instance.status = "Completed"
except Exception:
instance.status = "Error"
instance.error_message = frappe.get_traceback()
instance.error_message = frappe.get_traceback(with_context=True)
instance.report_end_time = frappe.utils.now()
instance.save(ignore_permissions=True)

View file

@ -14,6 +14,7 @@
"cmd",
"time",
"duration",
"event_type",
"section_break_1skt",
"request_headers",
"section_break_sgro",
@ -30,6 +31,7 @@
"label": "Path"
},
{
"depends_on": "eval:doc.event_type==\"HTTP Request\"",
"fieldname": "cmd",
"fieldtype": "Data",
"in_standard_filter": 1,
@ -67,6 +69,7 @@
"fieldtype": "Section Break"
},
{
"depends_on": "eval:doc.event_type==\"HTTP Request\"",
"fieldname": "request_headers",
"fieldtype": "Code",
"label": "Request Headers"
@ -76,11 +79,13 @@
"fieldtype": "Section Break"
},
{
"depends_on": "eval:doc.event_type==\"HTTP Request\"",
"fieldname": "form_dict",
"fieldtype": "Code",
"label": "Form Dict"
},
{
"depends_on": "eval:doc.event_type==\"HTTP Request\"",
"fieldname": "method",
"fieldtype": "Select",
"in_standard_filter": 1,
@ -96,6 +101,12 @@
{
"fieldname": "section_break_9jhm",
"fieldtype": "Section Break"
},
{
"fieldname": "event_type",
"fieldtype": "Data",
"hidden": 1,
"label": "Event Type"
}
],
"hide_toolbar": 1,
@ -103,7 +114,7 @@
"index_web_pages_for_search": 1,
"is_virtual": 1,
"links": [],
"modified": "2023-08-10 12:01:03.456643",
"modified": "2024-01-03 16:45:47.110048",
"modified_by": "Administrator",
"module": "Core",
"name": "Recorder",

View file

@ -19,6 +19,7 @@ class Recorder(Document):
cmd: DF.Data | None
duration: DF.Float
event_type: DF.Data | None
form_dict: DF.Code | None
method: DF.Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
number_of_queries: DF.Int
@ -27,7 +28,6 @@ class Recorder(Document):
sql_queries: DF.Table[RecorderQuery]
time: DF.Datetime | None
time_in_queries: DF.Float
# end: auto-generated types
def load_from_db(self):

View file

@ -108,14 +108,16 @@
"depends_on": "eval:doc.report_type==\"Query Report\"",
"fieldname": "query",
"fieldtype": "Code",
"label": "Query"
"label": "Query",
"options": "SQL"
},
{
"depends_on": "eval:doc.report_type==\"Script Report\" && doc.is_standard===\"No\"",
"description": "JavaScript Format: frappe.query_reports['REPORTNAME'] = {}",
"fieldname": "javascript",
"fieldtype": "Code",
"label": "Javascript"
"label": "Javascript",
"options": "Javascript"
},
{
"depends_on": "eval:doc.report_type==\"Report Builder\" || \"Custom Report\"",
@ -141,11 +143,12 @@
"label": "Prepared Report"
},
{
"depends_on": "eval:(doc.report_type===\"Script Report\" \n|| doc.report_type==\"Query Report\") \n&& doc.is_standard===\"No\"",
"depends_on": "eval:doc.report_type===\"Script Report\" && doc.is_standard===\"No\"",
"description": "Filters will be accessible via <code>filters</code>. <br><br>Send output as <code>result = [result]</code>, or for old style <code>data = [columns], [result]</code>",
"fieldname": "report_script",
"fieldtype": "Code",
"label": "Script"
"label": "Script",
"options": "Python"
},
{
"collapsible": 1,
@ -188,7 +191,7 @@
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-04-07 18:18:11.782178",
"modified": "2024-01-03 15:29:01.460404",
"modified_by": "Administrator",
"module": "Core",
"name": "Report",
@ -241,4 +244,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View file

@ -45,8 +45,8 @@ class Report(Document):
report_script: DF.Code | None
report_type: DF.Literal["Report Builder", "Query Report", "Script Report", "Custom Report"]
roles: DF.Table[HasRole]
# end: auto-generated types
def validate(self):
"""only administrator can save standard report"""
if not self.module:

View file

@ -25,6 +25,9 @@ class RoleProfile(Document):
self.name = self.role_profile
def on_update(self):
self.queue_action("update_all_users", now=frappe.flags.in_test)
def update_all_users(self):
"""Changes in role_profile reflected across all its user"""
has_role = frappe.qb.DocType("Has Role")
user = frappe.qb.DocType("User")

View file

@ -154,7 +154,7 @@ class ScheduledJobType(Document):
if frappe.debug_log:
self.scheduler_log.db_set("debug_log", "\n".join(frappe.debug_log))
if status == "Failed":
self.scheduler_log.db_set("details", frappe.get_traceback())
self.scheduler_log.db_set("details", frappe.get_traceback(with_context=True))
if status == "Start":
self.db_set("last_execution", now_datetime(), update_modified=False)
frappe.db.commit()

View file

@ -8,7 +8,13 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.rate_limiter import rate_limit
from frappe.utils.safe_exec import NamespaceDict, get_safe_globals, is_safe_exec_enabled, safe_exec
from frappe.utils.safe_exec import (
FrappeTransformer,
NamespaceDict,
get_safe_globals,
is_safe_exec_enabled,
safe_exec,
)
class ServerScript(Document):
@ -123,7 +129,7 @@ class ServerScript(Document):
from RestrictedPython import compile_restricted
try:
compile_restricted(self.script)
compile_restricted(self.script, policy=FrappeTransformer)
except Exception as e:
frappe.msgprint(str(e), title=_("Compilation warning"))

View file

@ -75,7 +75,7 @@ frappe.ui.form.on("User", {
if (
frm.can_edit_roles &&
!frm.is_new() &&
in_list(["System User", "Website User"], frm.doc.user_type)
["System User", "Website User"].includes(frm.doc.user_type)
) {
if (!frm.roles_editor) {
const role_area = $('<div class="role-editor">').appendTo(
@ -105,7 +105,7 @@ frappe.ui.form.on("User", {
}
if (
in_list(["System User", "Website User"], frm.doc.user_type) &&
["System User", "Website User"].includes(frm.doc.user_type) &&
!frm.is_new() &&
!frm.roles_editor &&
frm.can_edit_roles

View file

@ -1052,7 +1052,7 @@ def user_query(doctype, txt, searchfield, start, page_len, filters):
user_type_condition = "and user_type != 'Website User'"
if filters and filters.get("ignore_user_type") and frappe.session.data.user_type == "System User":
user_type_condition = ""
filters.pop("ignore_user_type")
filters and filters.pop("ignore_user_type", None)
txt = f"%{txt}%"
return frappe.db.sql(

View file

@ -251,7 +251,7 @@ frappe.PermissionEngine = class PermissionEngine {
this.rights.forEach((r) => {
if (!d.is_submittable && ["submit", "cancel", "amend"].includes(r)) return;
if (d.in_create && ["create", "write", "delete"].includes(r)) return;
if (d.in_create && ["create", "delete"].includes(r)) return;
this.add_check(perm_container, d, r);
});

View file

@ -1,6 +1,6 @@
{
"charts": [],
"content": "[{\"id\":\"5nnLaQeoFa\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"HXRmktXYHy\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":3}},{\"id\":\"pYALX3MwBW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":3}},{\"id\":\"XC78DuYB65\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":3}},{\"id\":\"XPm50Ppq3J\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":3}},{\"id\":\"yoU6nWiT83\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":3}},{\"id\":\"5UgFESBY0N\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Format Builder\",\"col\":3}},{\"id\":\"0gE0s-S70E\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Frappe Framework\",\"col\":3}},{\"id\":\"62hseENHbd\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tOCrOgLW1G\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Elements</b></span>\",\"col\":12}},{\"id\":\"BIHjudL0T_\",\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"id\":\"cJ6CVsa8qW\",\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"id\":\"MmEJpjEdGR\",\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"id\":\"2ZdtgxQZqq\",\"type\":\"card\",\"data\":{\"card_name\":\"Customization\",\"col\":4}},{\"id\":\"NPFolijIcb\",\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"id\":\"iK3JQ9RXJE\",\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}},{\"id\":\"TiO9FCUUeC\",\"type\":\"card\",\"data\":{\"card_name\":\"System Logs\",\"col\":4}}]",
"content": "[{\"id\":\"5nnLaQeoFa\",\"type\":\"header\",\"data\":{\"text\":\"<span style=\\\"font-size: 18px; letter-spacing: 0.18px;\\\"><b>Get started</b><br></span>\",\"col\":12}},{\"id\":\"HXRmktXYHy\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":3}},{\"id\":\"pYALX3MwBW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":3}},{\"id\":\"XC78DuYB65\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":3}},{\"id\":\"XPm50Ppq3J\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":3}},{\"id\":\"yoU6nWiT83\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":3}},{\"id\":\"5UgFESBY0N\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Format Builder\",\"col\":3}},{\"id\":\"0gE0s-S70E\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"id\":\"62hseENHbd\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tOCrOgLW1G\",\"type\":\"header\",\"data\":{\"text\":\"<span style=\\\"font-size: 18px; letter-spacing: 0.18px;\\\"><b>Components to build your app</b></span>\",\"col\":12}},{\"id\":\"cJ6CVsa8qW\",\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"id\":\"MmEJpjEdGR\",\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"id\":\"2ZdtgxQZqq\",\"type\":\"card\",\"data\":{\"card_name\":\"Customization\",\"col\":4}},{\"id\":\"NPFolijIcb\",\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"id\":\"BIHjudL0T_\",\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"id\":\"iK3JQ9RXJE\",\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}},{\"id\":\"TiO9FCUUeC\",\"type\":\"card\",\"data\":{\"card_name\":\"System Logs\",\"col\":4}}]",
"creation": "2021-01-02 10:51:16.579957",
"custom_blocks": [],
"docstatus": 0,
@ -8,7 +8,7 @@
"for_user": "",
"hide_custom": 0,
"icon": "tool",
"idx": 0,
"idx": 1,
"is_hidden": 0,
"label": "Build",
"links": [
@ -115,46 +115,6 @@
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Modules",
"link_count": 3,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Module Def",
"link_count": 0,
"link_to": "Module Def",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Module Onboarding",
"link_count": 0,
"link_to": "Module Onboarding",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Module Profile",
"link_count": 0,
"link_to": "Module Profile",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -321,9 +281,40 @@
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Modules",
"link_count": 2,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Module Def",
"link_count": 0,
"link_to": "Module Def",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Module Onboarding",
"link_count": 0,
"link_to": "Module Onboarding",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
}
],
"modified": "2023-07-04 14:34:09.420325",
"modified": "2024-01-02 15:38:42.806824",
"modified_by": "Administrator",
"module": "Core",
"name": "Build",
@ -346,8 +337,9 @@
{
"color": "Grey",
"doc_view": "List",
"label": "Learn Frappe Framework",
"type": "URL",
"label": "System Settings",
"link_to": "System Settings",
"type": "DocType",
"url": "https://frappe.school/courses/frappe-framework-course?utm_source=in_app"
},
{

View file

@ -1,383 +0,0 @@
{
"charts": [],
"content": "[{\"id\":\"bc3WecV0uU\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Settings</b></span>\",\"col\":12}},{\"id\":\"_6Jxax2I11\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"id\":\"rbf1Om8zJG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"id\":\"xMytWpIImZ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Website Settings\",\"col\":3}},{\"id\":\"Q9DPlmrPpX\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"oVwctUh0gf\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"hC0b24aSJG\",\"type\":\"card\",\"data\":{\"card_name\":\"Data\",\"col\":4}},{\"id\":\"JA_iI4Z0yI\",\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"id\":\"F1GxSqFKy9\",\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"id\":\"vugObM_K_T\",\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"id\":\"XwKthiuAAW\",\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"id\":\"EQY7Sfmdxn\",\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]",
"creation": "2020-03-02 15:09:40.527211",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "setting",
"idx": 0,
"is_hidden": 0,
"label": "Settings",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Data",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Import Data",
"link_count": 0,
"link_to": "Data Import",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Export Data",
"link_count": 0,
"link_to": "Data Export",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bulk Update",
"link_count": 0,
"link_to": "Bulk Update",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Download Backups",
"link_count": 0,
"link_to": "backups",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Deleted Documents",
"link_count": 0,
"link_to": "Deleted Document",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Email / Notifications",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Account",
"link_count": 0,
"link_to": "Email Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Domain",
"link_count": 0,
"link_to": "Email Domain",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Notification",
"link_count": 0,
"link_to": "Notification",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Template",
"link_count": 0,
"link_to": "Email Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Auto Email Report",
"link_count": 0,
"link_to": "Auto Email Report",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Newsletter",
"link_count": 0,
"link_to": "Newsletter",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Notification Settings",
"link_count": 0,
"link_to": "Notification Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Website",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Website Settings",
"link_count": 0,
"link_to": "Website Settings",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Website Theme",
"link_count": 0,
"link_to": "Website Theme",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Website Script",
"link_count": 0,
"link_to": "Website Script",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "About Us Settings",
"link_count": 0,
"link_to": "About Us Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Contact Us Settings",
"link_count": 0,
"link_to": "Contact Us Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Printing",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Format Builder",
"link_count": 0,
"link_to": "print-format-builder",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Settings",
"link_count": 0,
"link_to": "Print Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Format",
"link_count": 0,
"link_to": "Print Format",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Style",
"link_count": 0,
"link_to": "Print Style",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Workflow",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Workflow",
"link_count": 0,
"link_to": "Workflow",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Workflow State",
"link_count": 0,
"link_to": "Workflow State",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Workflow Action",
"link_count": 0,
"link_to": "Workflow Action",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Core",
"link_count": 2,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "System Settings",
"link_count": 0,
"link_to": "System Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Domain Settings",
"link_count": 0,
"link_to": "Domain Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2023-05-24 14:58:44.010999",
"modified_by": "Administrator",
"module": "Core",
"name": "Settings",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 18.0,
"shortcuts": [
{
"icon": "setting",
"label": "System Settings",
"link_to": "System Settings",
"type": "DocType"
},
{
"icon": "printer",
"label": "Print Settings",
"link_to": "Print Settings",
"type": "DocType"
},
{
"icon": "website",
"label": "Website Settings",
"link_to": "Website Settings",
"type": "DocType"
}
],
"title": "Settings"
}

View file

@ -1,6 +1,6 @@
{
"charts": [],
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Role\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Permission Manager\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User Profile\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User Type\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Users\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Logs\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Permissions\",\"col\":4}}]",
"content": "[{\"id\":\"YpGCeLfign\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"b7abeqw4NZ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User\",\"col\":3}},{\"id\":\"eghSJPhZRC\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Role\",\"col\":3}},{\"id\":\"uAzl_lT_C0\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Permission Manager\",\"col\":3}},{\"id\":\"EpBz2lplSt\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User Profile\",\"col\":3}},{\"id\":\"vHWhzaFoAH\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User Type\",\"col\":3}},{\"id\":\"oFB4l28FMU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yJNNylguxk\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"NMpIkExl3i\",\"type\":\"card\",\"data\":{\"card_name\":\"Users\",\"col\":4}},{\"id\":\"VepG3durKm\",\"type\":\"card\",\"data\":{\"card_name\":\"Logs\",\"col\":4}},{\"id\":\"S9FeWt7xXE\",\"type\":\"card\",\"data\":{\"card_name\":\"Permissions\",\"col\":4}}]",
"creation": "2020-03-02 15:12:16.754449",
"custom_blocks": [],
"docstatus": 0,
@ -12,47 +12,6 @@
"is_hidden": 0,
"label": "Users",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Users",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "User",
"link_count": 0,
"link_to": "User",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Role",
"link_count": 0,
"link_to": "Role",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Role Profile",
"link_count": 0,
"link_to": "Role Profile",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -145,9 +104,61 @@
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Users",
"link_count": 4,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "User",
"link_count": 0,
"link_to": "User",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Role",
"link_count": 0,
"link_to": "Role",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Role Profile",
"link_count": 0,
"link_to": "Role Profile",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Module Profile",
"link_count": 0,
"link_to": "Module Profile",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2023-05-24 14:47:23.619182",
"modified": "2024-01-02 15:39:13.811700",
"modified_by": "Administrator",
"module": "Core",
"name": "Users",

View file

@ -67,7 +67,7 @@ frappe.ui.form.on("Custom Field", {
return v.value;
});
if (insert_after == null || !in_list(fieldnames, insert_after)) {
if (insert_after == null || !fieldnames.includes(insert_after)) {
insert_after = fieldnames[-1];
}

View file

@ -362,7 +362,8 @@ def rename_fieldname(custom_field: str, fieldname: str):
frappe.msgprint(_("Old and new fieldnames are same."), alert=True)
return
frappe.db.rename_column(parent_doctype, old_fieldname, new_fieldname)
if frappe.db.has_column(field.dt, old_fieldname):
frappe.db.rename_column(parent_doctype, old_fieldname, new_fieldname)
# Update in DB after alter column is successful, alter column will implicitly commit, so it's
# best to commit change on field too to avoid any possible mismatch between two.

View file

@ -81,7 +81,7 @@ frappe.ui.form.on("Customize Form", {
add_customize_child_table_button: function (frm) {
frm.doc.fields.forEach(function (f) {
if (!in_list(["Table", "Table MultiSelect"], f.fieldtype)) return;
if (!["Table", "Table MultiSelect"].includes(f.fieldtype)) return;
frm.add_custom_button(
__(f.options),

View file

@ -3,7 +3,7 @@
frappe.ui.form.on("Property Setter", {
validate: function (frm) {
if (frm.doc.property_type == "Check" && !in_list(["0", "1"], frm.doc.value)) {
if (frm.doc.property_type == "Check" && !["0", "1"].includes(frm.doc.value)) {
frappe.throw(__("Value for a check field can be either 0 or 1"));
}
},

View file

@ -57,7 +57,7 @@ class DbManager:
from frappe.database import get_command
from frappe.utils import execute_in_shell
command = []
command = ["set -o pipefail;"]
if source.endswith(".gz"):
if gzip := which("gzip"):

View file

@ -1,7 +1,9 @@
import os
import re
import frappe
from frappe.database.db_manager import DbManager
from frappe.utils import cint
def setup_database():
@ -13,6 +15,11 @@ def setup_database():
root_conn.sql(f"CREATE DATABASE `{frappe.conf.db_name}`")
root_conn.sql(f"CREATE user {frappe.conf.db_name} password '{frappe.conf.db_password}'")
root_conn.sql("GRANT ALL PRIVILEGES ON DATABASE `{0}` TO {0}".format(frappe.conf.db_name))
if psql_version := root_conn.sql("SELECT VERSION()", as_dict=True):
version_string = psql_version[0].get("version") or "PostgreSQL 14"
major_version = cint(re.split(r"[\w\.]", version_string)[1])
if major_version > 15:
root_conn.sql("ALTER DATABASE `{0}` OWNER TO {0}".format(frappe.conf.db_name))
root_conn.close()

View file

@ -565,7 +565,7 @@ def save_new_widget(doc, page, blocks, new_widgets):
page, json_config, e
)
doc.log_error("Could not save customization", log)
return False
raise
return True

View file

@ -109,7 +109,7 @@ def get(
refresh=None,
):
if chart_name:
chart = frappe.get_doc("Dashboard Chart", chart_name)
chart: DashboardChart = frappe.get_doc("Dashboard Chart", chart_name)
else:
chart = frappe._dict(frappe.parse_json(chart))
@ -207,13 +207,14 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date):
filters.append([doctype, datefield, ">=", from_date, False])
filters.append([doctype, datefield, "<=", to_date, False])
data = frappe.db.get_list(
data = frappe.get_list(
doctype,
fields=[datefield, f"SUM({value_field})", "COUNT(*)"],
filters=filters,
group_by=datefield,
order_by=datefield,
as_list=True,
parent_doctype=chart.parent_document_type,
)
result = get_result(data, timegrain, from_date, to_date, chart.chart_type)

View file

@ -10,7 +10,7 @@ from frappe.model.naming import append_number_if_name_exists
from frappe.modules.export_file import export_to_files
from frappe.query_builder import Criterion
from frappe.query_builder.utils import DocType
from frappe.utils import cint
from frappe.utils import cint, flt
class NumberCard(Document):
@ -165,7 +165,7 @@ def get_result(doc, filters, to_date=None):
)
number = res[0]["result"] if res else 0
return cint(number)
return flt(number)
@frappe.whitelist()

View file

@ -41,6 +41,10 @@ frappe.ui.form.on("System Console", {
frm.get_field("sql_output").html("");
}
}
const field = frm.get_field("console");
field.df.options = frm.doc.type;
field.set_language();
},
render_sql_output: function (frm) {

View file

@ -109,7 +109,7 @@ class DocTags:
tags = ""
else:
tl = unique(filter(lambda x: x, tl))
tags = "," + ",".join(tl)
tags = ",".join(tl)
try:
frappe.db.sql(
"update `tab{}` set _user_tags={} where name={}".format(self.dt, "%s", "%s"), (tags, dn)

View file

@ -277,7 +277,6 @@ def save_page(title, public, new_widgets, blocks):
doc = frappe.get_doc("Workspace", pages[0])
doc.content = blocks
doc.save(ignore_permissions=True)
save_new_widget(doc, title, blocks, new_widgets)

View file

@ -8,7 +8,7 @@ from frappe.build import scrub_html_template
from frappe.model.meta import Meta
from frappe.model.utils import render_include
from frappe.modules import get_module_path, load_doctype_module, scrub
from frappe.utils import get_html_format
from frappe.utils import get_bench_path, get_html_format
from frappe.utils.data import get_link_to_form
ASSET_KEYS = (
@ -120,7 +120,9 @@ class FormMeta(Meta):
def _add_code(self, path, fieldname):
js = get_js(path)
if js:
comment = f"\n\n/* Adding {path} */\n\n"
bench_path = get_bench_path() + "/"
asset_path = path.replace(bench_path, "")
comment = f"\n\n/* Adding {asset_path} */\n\n"
sourceURL = f"\n\n//# sourceURL={scrub(self.name) + fieldname}"
self.set(fieldname, (self.get(fieldname) or "") + comment + js + sourceURL)

View file

@ -350,7 +350,7 @@ frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide {
let me = this;
this.fields.filter(frappe.model.is_value_type).forEach((field) => {
field.fieldname &&
me.get_input(field.fieldname)?.on("change", function () {
me.get_input(field.fieldname)?.on?.("change", function () {
frappe.telemetry.capture(`${field.fieldname}_set`, "setup");
if (
field.fieldname == "enable_telemetry" &&

View file

@ -157,7 +157,7 @@ def get_setup_complete_hooks(args):
def handle_setup_exception(args):
frappe.db.rollback()
if args:
traceback = frappe.get_traceback()
traceback = frappe.get_traceback(with_context=True)
print(traceback)
for hook in frappe.get_hooks("setup_wizard_exception"):
frappe.get_attr(hook)(traceback, args)

View file

@ -36,13 +36,17 @@ def get_all_nodes(doctype, label, parent, tree_method, **filters):
@frappe.whitelist()
def get_children(doctype, parent="", **filters):
return _get_children(doctype, parent)
def get_children(doctype, parent="", include_disabled=False, **filters):
if isinstance(include_disabled, str):
include_disabled = frappe.sbool(include_disabled)
return _get_children(doctype, parent, include_disabled=include_disabled)
def _get_children(doctype, parent="", ignore_permissions=False):
def _get_children(doctype, parent="", ignore_permissions=False, include_disabled=False):
parent_field = "parent_" + doctype.lower().replace(" ", "_")
filters = [[f"ifnull(`{parent_field}`,'')", "=", parent], ["docstatus", "<", 2]]
if frappe.db.has_column(doctype, "disabled") and not include_disabled:
filters.append(["disabled", "=", False])
meta = frappe.get_meta(doctype)

View file

@ -268,15 +268,15 @@ class EmailAccount(Document):
if not in_receive and self.use_imap:
email_server.imap.logout()
# reset failed attempts count
self.set_failed_attempts_count(0)
return email_server
def check_email_server_connection(self, email_server, in_receive):
# tries to connect to email server and handles failure
try:
email_server.connect()
# reset failed attempts count - do it after succesful connection
self.set_failed_attempts_count(0)
except (error_proto, imaplib.IMAP4.error) as e:
message = cstr(e).lower().replace(" ", "")
auth_error_codes = [
@ -294,6 +294,8 @@ class EmailAccount(Document):
error_message = _(
"Authentication failed while receiving emails from Email Account: {0}."
).format(self.name)
error_message = _("Email Account Disabled.") + " " + error_message
error_message += "<br>" + _("Message from server: {0}").format(cstr(e))
self.handle_incoming_connect_error(description=error_message)
return None
@ -489,31 +491,35 @@ class EmailAccount(Document):
state.pop("_smtp_server_instance", None)
def handle_incoming_connect_error(self, description):
if self.get_failed_attempts_count() > 2:
self.db_set("enable_incoming", 0)
for user in get_system_managers(only_name=True):
try:
assign_to.add(
{
"assign_to": user,
"doctype": self.doctype,
"name": self.name,
"description": description,
"priority": "High",
"notify": 1,
}
)
except assign_to.DuplicateToDoError:
frappe.clear_last_message()
if self.get_failed_attempts_count() > 5:
# This is done in background to avoid committing here.
frappe.enqueue(self._disable_broken_incoming_account, description=description)
else:
self.set_failed_attempts_count(self.get_failed_attempts_count() + 1)
def _disable_broken_incoming_account(self, description):
self.db_set("enable_incoming", 0)
for user in get_system_managers(only_name=True):
try:
assign_to.add(
{
"assign_to": [user],
"doctype": self.doctype,
"name": self.name,
"description": description,
"priority": "High",
"notify": 1,
}
)
except assign_to.DuplicateToDoError:
pass
def set_failed_attempts_count(self, value):
frappe.cache.set(f"{self.name}:email-account-failed-attempts", value)
frappe.cache.set_value(f"{self.name}:email-account-failed-attempts", value)
def get_failed_attempts_count(self):
return cint(frappe.cache.get(f"{self.name}:email-account-failed-attempts"))
return cint(frappe.cache.get_value(f"{self.name}:email-account-failed-attempts"))
def receive(self):
"""Called by scheduler to receive emails from this EMail account using POP3/IMAP."""

View file

@ -4,7 +4,7 @@
frappe.ui.form.on("Newsletter", {
refresh(frm) {
let doc = frm.doc;
let can_write = in_list(frappe.boot.user.can_write, doc.doctype);
let can_write = frappe.boot.user.can_write.includes(doc.doctype);
if (!frm.is_new() && !frm.is_dirty() && !doc.email_sent && can_write) {
frm.add_custom_button(
__("Send a test email"),

View file

@ -1,5 +1,6 @@
{
"actions": [],
"allow_guest_to_view": 1,
"allow_rename": 1,
"creation": "2013-01-10 16:34:31",
"description": "Create and Send Newsletters",
@ -253,7 +254,7 @@
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [],
"modified": "2023-03-20 22:45:59.129630",
"modified": "2023-12-29 18:04:13.270523",
"modified_by": "Administrator",
"module": "Email",
"name": "Newsletter",

View file

@ -34,7 +34,7 @@ frappe.notification = {
let fields = frappe.get_doc("DocType", frm.doc.document_type).fields;
let options = $.map(fields, function (d) {
return in_list(frappe.model.no_value_type, d.fieldtype)
return frappe.model.no_value_type.includes(d.fieldtype)
? null
: get_select_options(d);
});
@ -66,7 +66,7 @@ frappe.notification = {
: null;
}
});
} else if (in_list(["WhatsApp", "SMS"], frm.doc.channel)) {
} else if (["WhatsApp", "SMS"].includes(frm.doc.channel)) {
receiver_fields = $.map(fields, function (d) {
return d.options == "Phone" ? get_select_options(d) : null;
});
@ -102,7 +102,7 @@ Last comment: {{ comments[-1].comment }} by {{ comments[-1].by }}
&lt;/ul&gt;
</pre>
`;
} else if (in_list(["Slack", "System Notification", "SMS"], frm.doc.channel)) {
} else if (["Slack", "System Notification", "SMS"].includes(frm.doc.channel)) {
template = `<h5>Message Example</h5>
<pre>*Order Overdue*
@ -166,7 +166,7 @@ frappe.ui.form.on("Notification", {
frappe.set_route("Form", "Customize Form");
},
event: function (frm) {
if (in_list(["Days Before", "Days After"], frm.doc.event)) {
if (["Days Before", "Days After"].includes(frm.doc.event)) {
frm.add_custom_button(__("Get Alerts for Today"), function () {
frappe.call({
method: "frappe.email.doctype.notification.notification.get_documents_for_today",

View file

@ -366,7 +366,9 @@ def get_context(context):
# For sending messages to specified role
if recipient.receiver_by_role:
receiver_list += get_info_based_on_role(recipient.receiver_by_role, "mobile_no")
receiver_list += get_info_based_on_role(
recipient.receiver_by_role, "mobile_no", ignore_permissions=True
)
return receiver_list
@ -505,8 +507,7 @@ def evaluate_alert(doc: Document, alert, event):
frappe.throw(message, title=_("Error in Notification"))
except Exception as e:
title = str(e)
message = frappe.get_traceback()
frappe.log_error(message=message, title=title)
frappe.log_error(title=title)
msg = f"<details><summary>{title}</summary>{message}</details>"
frappe.throw(msg, title=_("Error in Notification"))

View file

@ -2089,8 +2089,17 @@
},
"Palestinian Territory, Occupied": {
"code": "ps",
"currency": "ILS",
"currency_fraction": "Agora",
"currency_fraction_units": 100,
"currency_name": "New Israeli Sheqel",
"currency_symbol": "\u20aa",
"number_format": "#,###.##",
"isd": "+970"
"isd": "+970",
"timezones": [
"Asia/Hebron",
"Asia/Jerusalem"
]
},
"Panama": {
"code": "pa",
@ -2541,15 +2550,17 @@
},
"Sudan": {
"code": "sd",
"currency": "SDG",
"currency_fraction": "Piastre",
"currency_fraction_units": 100,
"currency_symbol": "\u00a3",
"currency_name": "Sudanese Pound",
"currency_symbol": "\u062c.\u0633.",
"number_format": "#,###.##",
"timezones": [
"Africa/Khartoum"
"Africa/Khartoum"
],
"isd": "+249"
},
},
"Suriname": {
"code": "sr",
"currency": "SRD",

View file

@ -1,3 +1,5 @@
import os
from . import __version__ as app_version
app_name = "frappe"
@ -426,9 +428,18 @@ before_request = [
# Background Job Hooks
before_job = [
"frappe.recorder.record",
"frappe.monitor.start",
]
if os.getenv("FRAPPE_SENTRY_DSN") and (
os.getenv("ENABLE_SENTRY_DB_MONITORING") or os.getenv("SENTRY_TRACING_SAMPLE_RATE")
):
before_request.append("frappe.utils.sentry.set_sentry_context")
before_job.append("frappe.utils.sentry.set_sentry_context")
after_job = [
"frappe.recorder.dump",
"frappe.monitor.stop",
"frappe.utils.file_lock.release_document_locks",
"frappe.utils.telemetry.flush",

View file

@ -757,8 +757,8 @@ def is_downgrade(sql_file_path, verbose=False):
if backup_version is None:
# This is likely an older backup, so try to extract another way
header = get_db_dump_header(sql_file_path).split("\n")
if "Version" in header[0]:
backup_version = header[0].split(":")[-1].strip()
if match := re.search(r"Frappe (\d+\.\d+\.\d+)", header[0]):
backup_version = match.group(1)
# Assume it's not a downgrade if we can't determine backup version
if backup_version is None:

View file

@ -153,15 +153,14 @@ def get_context(doc):
def enqueue_webhook(doc, webhook) -> None:
request_url = headers = data = None
try:
webhook: Webhook = frappe.get_doc("Webhook", webhook.get("name"))
headers = get_webhook_headers(doc, webhook)
data = get_webhook_data(doc, webhook)
request_url = webhook.request_url
if webhook.is_dynamic_url:
request_url = frappe.render_template(webhook.request_url, get_context(doc))
else:
request_url = webhook.request_url
headers = get_webhook_headers(doc, webhook)
data = get_webhook_data(doc, webhook)
except Exception as e:
frappe.logger().debug({"enqueue_webhook_error": e})

View file

@ -3,6 +3,7 @@
# model __init__.py
import frappe
from frappe import _
data_fieldtypes = (
"Currency",
@ -132,6 +133,25 @@ log_types = (
"Console Log",
)
std_fields = [
{"fieldname": "name", "fieldtype": "Link", "label": _("ID")},
{"fieldname": "owner", "fieldtype": "Link", "label": _("Created By"), "options": "User"},
{"fieldname": "idx", "fieldtype": "Int", "label": _("Index")},
{"fieldname": "creation", "fieldtype": "Datetime", "label": _("Created On")},
{"fieldname": "modified", "fieldtype": "Datetime", "label": _("Last Updated On")},
{
"fieldname": "modified_by",
"fieldtype": "Link",
"label": _("Last Updated By"),
"options": "User",
},
{"fieldname": "_user_tags", "fieldtype": "Data", "label": _("Tags")},
{"fieldname": "_liked_by", "fieldtype": "Data", "label": _("Liked By")},
{"fieldname": "_comments", "fieldtype": "Text", "label": _("Comments")},
{"fieldname": "_assign", "fieldtype": "Text", "label": _("Assigned To")},
{"fieldname": "docstatus", "fieldtype": "Int", "label": _("Document Status")},
]
def delete_fields(args_dict, delete=0):
"""

View file

@ -741,7 +741,8 @@ class DatabaseQuery:
df = meta.get("fields", {"fieldname": f.fieldname})
df = df[0] if df else None
can_be_null = f.fieldname != "name" # primary key is never nullable
# primary key is never nullable, modified is usually indexed by default and always present
can_be_null = f.fieldname not in ("name", "modified")
value = None

View file

@ -214,7 +214,7 @@ class Document(BaseDocument):
if not self.has_permission(permtype):
self.raise_no_permission_to(permtype)
def has_permission(self, permtype="read") -> bool:
def has_permission(self, permtype="read", *, debug=False, user=None) -> bool:
"""
Call `frappe.permissions.has_permission` if `ignore_permissions` flag isn't truthy
@ -226,7 +226,7 @@ class Document(BaseDocument):
import frappe.permissions
return frappe.permissions.has_permission(self.doctype, permtype, self)
return frappe.permissions.has_permission(self.doctype, permtype, self, debug=debug, user=user)
def raise_no_permission_to(self, perm_type):
"""Raise `frappe.PermissionError`."""
@ -420,36 +420,35 @@ class Document(BaseDocument):
def update_child_table(self, fieldname: str, df: Optional["DocField"] = None):
"""sync child table for given fieldname"""
rows = []
df: "DocField" = df or self.meta.get_field(fieldname)
for d in self.get(df.fieldname):
d: Document
d.db_update()
rows.append(d.name)
if (
df.options in (self.flags.ignore_children_type or [])
or frappe.get_meta(df.options).is_virtual == 1
):
# do not delete rows for this because of flags
# hack for docperm :(
return
all_rows = self.get(df.fieldname)
# delete rows that do not match the ones in the document
tbl = frappe.qb.DocType(df.options)
qry = (
frappe.qb.from_(tbl)
.where(tbl.parent == self.name)
.where(tbl.parenttype == self.doctype)
.where(tbl.parentfield == fieldname)
.delete()
)
# if the doctype isn't in ignore_children_type flag and isn't virtual
if not (
df.options in (self.flags.ignore_children_type or ())
or frappe.get_meta(df.options).is_virtual == 1
):
existing_row_names = [row.name for row in all_rows if row.name and not row.is_new()]
if rows:
qry = qry.where(tbl.name.notin(rows))
tbl = frappe.qb.DocType(df.options)
qry = (
frappe.qb.from_(tbl)
.where(tbl.parent == self.name)
.where(tbl.parenttype == self.doctype)
.where(tbl.parentfield == fieldname)
.delete()
)
qry.run()
if existing_row_names:
qry = qry.where(tbl.name.notin(existing_row_names))
qry.run()
# update / insert
for d in all_rows:
d: Document
d.db_update()
def get_doc_before_save(self) -> "Document":
return getattr(self, "_doc_before_save", None)

View file

@ -150,12 +150,13 @@ def apply_workflow(doc, action):
@frappe.whitelist()
def can_cancel_document(doctype):
workflow = get_workflow(doctype)
for state_doc in workflow.states:
if state_doc.doc_status == "2":
for transition in workflow.transitions:
if transition.next_state == state_doc.state:
return False
return True
cancelling_states = [s.state for s in workflow.states if s.doc_status == "2"]
if not cancelling_states:
return True
for transition in workflow.transitions:
if transition.next_state in cancelling_states:
return False
return True

View file

@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import copy
import functools
import frappe
import frappe.share
@ -37,22 +38,41 @@ AUTOMATIC_ROLES = (GUEST_ROLE, ALL_USER_ROLE, SYSTEM_USER_ROLE, ADMIN_ROLE)
def print_has_permission_check_logs(func):
@functools.wraps(func)
def inner(*args, **kwargs):
frappe.flags["has_permission_check_logs"] = []
result = func(*args, **kwargs)
self_perm_check = True if not kwargs.get("user") else kwargs.get("user") == frappe.session.user
raise_exception = kwargs.get("raise_exception", True)
self_perm_check = True if not kwargs.get("user") else kwargs.get("user") == frappe.session.user
if raise_exception:
frappe.flags["has_permission_check_logs"] = []
result = func(*args, **kwargs)
# print only if access denied
# and if user is checking his own permission
if not result and self_perm_check and raise_exception:
msgprint(("<br>").join(frappe.flags.get("has_permission_check_logs", [])))
frappe.flags.pop("has_permission_check_logs", None)
if raise_exception:
frappe.flags.pop("has_permission_check_logs", None)
return result
return inner
def _debug_log(log: str):
if not hasattr(frappe.local, "permission_debug_log"):
frappe.local.permission_debug_log = []
frappe.local.permission_debug_log.append(log)
def _pop_debug_log() -> list[str]:
if log := getattr(frappe.local, "permission_debug_log", None):
del frappe.local.permission_debug_log
return log
return []
@print_has_permission_check_logs
def has_permission(
doctype,
@ -62,7 +82,8 @@ def has_permission(
raise_exception=True,
*,
parent_doctype=None,
):
debug=False,
) -> bool:
"""Return True if user has permission `ptype` for given `doctype`.
If `doc` is passed, also check user, share and owner permissions.
@ -83,9 +104,13 @@ def has_permission(
user = frappe.session.user
if user == "Administrator":
debug and _debug_log("Allowed everything because user is Administrator")
return True
if ptype == "share" and frappe.get_system_settings("disable_document_sharing"):
debug and _debug_log(
"User can't share because sharing is disabled globally from system settings"
)
return False
if not doc and hasattr(doctype, "doctype"):
@ -94,88 +119,105 @@ def has_permission(
doctype = doc.doctype
if frappe.is_table(doctype):
return has_child_permission(doctype, ptype, doc, user, raise_exception, parent_doctype)
return has_child_permission(
doctype, ptype, doc, user, raise_exception, parent_doctype, debug=debug
)
meta = frappe.get_meta(doctype)
if doc:
if isinstance(doc, (str, int)):
doc = frappe.get_doc(meta.name, doc)
perm = get_doc_permissions(doc, user=user, ptype=ptype).get(ptype)
perm = get_doc_permissions(doc, user=user, ptype=ptype, debug=debug).get(ptype)
if not perm:
debug and _debug_log(
"Permission check failed from role permission system. Check if user's role grant them permission to the document."
)
msg = _("User {0} does not have access to this document").format(frappe.bold(user))
if frappe.has_permission(doc.doctype):
msg += f": {_(doc.doctype)} - {doc.name}"
push_perm_check_log(msg)
push_perm_check_log(msg, debug=debug)
else:
if ptype == "submit" and not cint(meta.is_submittable):
push_perm_check_log(_("Document Type is not submittable"))
push_perm_check_log(_("Document Type is not submittable"), debug=debug)
return False
if ptype == "import" and not cint(meta.allow_import):
push_perm_check_log(_("Document Type is not importable"))
push_perm_check_log(_("Document Type is not importable"), debug=debug)
return False
role_permissions = get_role_permissions(meta, user=user)
role_permissions = get_role_permissions(meta, user=user, debug=debug)
debug and _debug_log(
"User has following permissions using role permission system: "
+ frappe.as_json(role_permissions, indent=8)
)
perm = role_permissions.get(ptype)
if not perm:
push_perm_check_log(
_("User {0} does not have doctype access via role permission for document {1}").format(
frappe.bold(user), frappe.bold(doctype)
)
),
debug=debug,
)
def false_if_not_shared():
if ptype in ("read", "write", "share", "submit", "email", "print"):
if ptype not in ("read", "write", "share", "submit", "email", "print"):
debug and _debug_log(f"Permission type {ptype} can not be shared")
return False
rights = ["read" if ptype in ("email", "print") else ptype]
rights = ["read" if ptype in ("email", "print") else ptype]
if doc:
doc_name = get_doc_name(doc)
shared = frappe.share.get_shared(
doctype,
user,
rights=rights,
filters=[["share_name", "=", doc_name]],
limit=1,
)
if doc:
doc_name = get_doc_name(doc)
shared = frappe.share.get_shared(
doctype,
user,
rights=rights,
filters=[["share_name", "=", doc_name]],
limit=1,
)
debug and _debug_log(f"Document is shared with user for {ptype}? {bool(shared)}")
return bool(shared)
if shared:
if ptype in ("read", "write", "share", "submit") or meta.permissions[0].get(ptype):
return True
elif frappe.share.get_shared(doctype, user, rights=rights, limit=1):
# if atleast one shared doc of that type, then return True
# this is used in db_query to check if permission on DocType
return True
elif frappe.share.get_shared(doctype, user, rights=rights, limit=1):
# if atleast one shared doc of that type, then return True
# this is used in db_query to check if permission on DocType
debug and _debug_log(f"At least one document is shared with user with perm: {rights}")
return True
return False
if not perm:
debug and _debug_log("Checking if document/doctype is explicitly shared with user")
perm = false_if_not_shared()
return bool(perm)
def get_doc_permissions(doc, user=None, ptype=None):
def get_doc_permissions(doc, user=None, ptype=None, debug=False):
"""Return a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`"""
if not user:
user = frappe.session.user
if frappe.is_table(doc.doctype):
return {"read": 1, "write": 1}
meta = frappe.get_meta(doc.doctype)
def is_user_owner():
return (doc.get("owner") or "").lower() == user.lower()
if has_controller_permissions(doc, ptype, user=user) is False:
push_perm_check_log(_("Not allowed via controller permission check"))
if not has_controller_permissions(doc, ptype, user=user, debug=debug):
push_perm_check_log(_("Not allowed via controller permission check"), debug=debug)
return {ptype: 0}
permissions = copy.deepcopy(get_role_permissions(meta, user=user, is_owner=is_user_owner()))
permissions = copy.deepcopy(
get_role_permissions(meta, user=user, is_owner=is_user_owner(), debug=debug)
)
debug and _debug_log(
"User has following permissions using role permission system: "
+ frappe.as_json(permissions, indent=8)
)
if not cint(meta.is_submittable):
permissions["submit"] = 0
@ -189,20 +231,29 @@ def get_doc_permissions(doc, user=None, ptype=None):
# some access might be only for the owner
# eg. everyone might have read access but only owner can delete
permissions.update(permissions.get("if_owner", {}))
debug and _debug_log(
"User is owner of document, so permissions are updated to: " + frappe.as_json(permissions)
)
if not has_user_permission(doc, user):
if not has_user_permission(doc, user, debug=debug):
if is_user_owner():
# replace with owner permissions
permissions = permissions.get("if_owner", {})
# if_owner does not come with create rights...
permissions["create"] = 0
debug and _debug_log("User has only 'If owner' permissions because of User Permissions")
else:
debug and _debug_log("User has no permissions because of User Permissions")
permissions = {}
debug and _debug_log(
"Final applicable permissions after evaluating user permissions: "
+ frappe.as_json(permissions, indent=8)
)
return permissions
def get_role_permissions(doctype_meta, user=None, is_owner=None):
def get_role_permissions(doctype_meta, user=None, is_owner=None, debug=False):
"""
Return dict of evaluated role permissions like:
{
@ -225,12 +276,14 @@ def get_role_permissions(doctype_meta, user=None, is_owner=None):
cache_key = (doctype_meta.name, user, bool(is_owner))
if user == "Administrator":
debug and _debug_log("all permissions granted because user is Administrator")
return allow_everything()
if not frappe.local.role_permissions.get(cache_key):
if not frappe.local.role_permissions.get(cache_key) or debug:
perms = frappe._dict(if_owner={})
roles = frappe.get_roles(user)
debug and _debug_log("User has following roles: " + str(roles))
def is_perm_applicable(perm):
return perm.role in roles and cint(perm.permlevel) == 0
@ -271,7 +324,7 @@ def get_user_permissions(user):
return get_user_permissions(user)
def has_user_permission(doc, user=None):
def has_user_permission(doc, user=None, debug=False):
"""Return True if User is allowed to view considering User Permissions."""
from frappe.core.doctype.user_permission.user_permission import get_user_permissions
@ -279,13 +332,17 @@ def has_user_permission(doc, user=None):
if not user_permissions:
# no user permission rules specified for this doctype
debug and _debug_log("User is not affected by any user permissions")
return True
# user can create own role permissions, so nothing applies
if get_role_permissions("User Permission", user=user).get("write"):
debug and _debug_log("User permission bypassed because user can modify user permissions.")
return True
apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions")
if apply_strict_user_permissions:
debug and _debug_log("Strict user permissions will be applied")
doctype = doc.get("doctype")
docname = doc.get("name")
@ -300,8 +357,14 @@ def has_user_permission(doc, user=None):
# only check if allowed_docs is not empty
if allowed_docs and docname not in allowed_docs:
# no user permissions for this doc specified
push_perm_check_log(_("Not allowed for {0}: {1}").format(_(doctype), docname))
debug and _debug_log(
"User doesn't have access to this document because of User Permissions, allowed documents: "
+ str(allowed_docs)
)
push_perm_check_log(_("Not allowed for {0}: {1}").format(_(doctype), docname), debug=debug)
return False
else:
debug and _debug_log(f"User Has access to {docname} via User Permissions.")
# STEP 2: ---------------------------------
# check user permissions in all link fields
@ -357,7 +420,7 @@ def has_user_permission(doc, user=None):
_(field.label) if field.label else field.fieldname,
)
push_perm_check_log(msg)
push_perm_check_log(msg, debug=debug)
return False
@ -373,23 +436,27 @@ def has_user_permission(doc, user=None):
return True
def has_controller_permissions(doc, ptype, user=None):
"""Return controller permissions if defined, None if not defined."""
def has_controller_permissions(doc, ptype, user=None, debug=False) -> bool:
"""Return controller permissions if denied, True if not defined.
Controllers can only deny permission, they can not explicitly grant any permission that wasn't
already present."""
if not user:
user = frappe.session.user
methods = frappe.get_hooks("has_permission").get(doc.doctype, [])
if not methods:
return None
return True
for method in reversed(methods):
controller_permission = frappe.call(frappe.get_attr(method), doc=doc, ptype=ptype, user=user)
controller_permission = frappe.call(method, doc=doc, ptype=ptype, user=user, debug=debug)
debug and _debug_log(f"Controller permission check from {method}: {controller_permission}")
if controller_permission is not None:
return controller_permission
return bool(controller_permission)
# controller permissions could not decide on True or False
return None
# None of the controller hooks returned anything conclusive
return True
def get_doctypes_with_read():
@ -678,7 +745,8 @@ def filter_allowed_docs_for_doctype(user_permissions, doctype, with_default_doc=
return (allowed_doc, default_doc) if with_default_doc else allowed_doc
def push_perm_check_log(log):
def push_perm_check_log(log, debug=False):
debug and _debug_log(log)
if frappe.flags.get("has_permission_check_logs") is None:
return
@ -692,7 +760,10 @@ def has_child_permission(
user=None,
raise_exception=True,
parent_doctype=None,
):
*,
debug=False,
) -> bool:
debug and _debug_log("This doctype is a child table, permissions will be checked on parent.")
if isinstance(child_doc, str):
child_doc = frappe.db.get_value(
child_doctype,
@ -706,7 +777,8 @@ def has_child_permission(
if not parent_doctype:
push_perm_check_log(
_("Please specify a valid parent DocType for {0}").format(frappe.bold(child_doctype))
_("Please specify a valid parent DocType for {0}").format(frappe.bold(child_doctype)),
debug=debug,
)
return False
@ -720,7 +792,8 @@ def has_child_permission(
push_perm_check_log(
_("{0} is not a valid parent DocType for {1}").format(
frappe.bold(parent_doctype), frappe.bold(child_doctype)
)
),
debug=debug,
)
return False
@ -730,7 +803,8 @@ def has_child_permission(
push_perm_check_log(
_("Parentfield not specified in {0}: {1}").format(
frappe.bold(child_doctype), frappe.bold(child_doc.name)
)
),
debug=debug,
)
return False
@ -738,14 +812,19 @@ def has_child_permission(
push_perm_check_log(
_("{0} is not a valid parentfield for {1}").format(
frappe.bold(parentfield), frappe.bold(child_doctype)
)
),
debug=debug,
)
return False
permlevel = parent_meta.get_field(parentfield).permlevel
if permlevel > 0 and permlevel not in parent_meta.get_permlevel_access(ptype, user=user):
accessible_permlevels = parent_meta.get_permlevel_access(ptype, user=user)
if permlevel > 0 and permlevel not in accessible_permlevels:
push_perm_check_log(
_("Insufficient Permission Level for {0}").format(frappe.bold(parent_doctype))
_("Insufficient Permission Level for {0}").format(frappe.bold(parent_doctype)), debug=debug
)
debug and _debug_log(
f"This table is perm level {permlevel} but user only has access to {accessible_permlevels}"
)
return False
@ -755,6 +834,7 @@ def has_child_permission(
doc=child_doc and getattr(child_doc, "parent_doc", child_doc.parent),
user=user,
raise_exception=raise_exception,
debug=debug,
)

View file

@ -79,7 +79,7 @@ frappe.ui.form.on("Print Format", {
frappe.model.with_doctype(doctype, () => {
const meta = frappe.get_meta(doctype);
const has_int_float_currency_field = meta.fields.filter((df) =>
in_list(["Int", "Float", "Currency"], df.fieldtype)
["Int", "Float", "Currency"].includes(df.fieldtype)
);
frm.toggle_display("absolute_value", has_int_float_currency_field.length);
});

View file

@ -280,7 +280,7 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder {
set_section(f.label);
} else if (f.fieldtype === "Column Break") {
set_column();
} else if (!in_list(frappe.model.layout_fields, f.fieldtype)) {
} else if (!frappe.model.layout_fields.includes(f.fieldtype)) {
if (!column) set_column();
if (f.fieldtype === "Table") {
@ -317,7 +317,7 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder {
f.visible_columns = [];
$.each(frappe.get_meta(f.options).fields, function (i, _f) {
if (
!in_list(["Section Break", "Column Break", "Tab Break"], _f.fieldtype) &&
!["Section Break", "Column Break", "Tab Break"].includes(_f.fieldtype) &&
!_f.print_hide &&
f.label
) {
@ -636,7 +636,7 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder {
// add field which are in column_names first to preserve order
var fields = [];
$.each(column_names, function (i, v) {
if (in_list(Object.keys(docfields_by_name), v)) {
if (Object.keys(docfields_by_name).includes(v)) {
fields.push(docfields_by_name[v]);
}
});
@ -644,8 +644,8 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder {
$.each(doc_fields, function (j, f) {
if (
f &&
!in_list(column_names, f.fieldname) &&
!in_list(["Section Break", "Column Break", "Tab Break"], f.fieldtype) &&
!column_names.includes(f.fieldname) &&
!["Section Break", "Column Break", "Tab Break"].includes(f.fieldtype) &&
f.label
) {
fields.push(f);

View file

@ -171,7 +171,7 @@ export const useStore = defineStore("form-builder-store", () => {
}
// Link & Table fields should always have options set
if (in_list(["Link", ...frappe.model.table_fields], df.fieldtype) && !df.options) {
if (["Link", ...frappe.model.table_fields].includes(df.fieldtype) && !df.options) {
error_message = __(
"Options is required for field {0} of type {1}",
get_field_data(df)
@ -187,7 +187,7 @@ export const useStore = defineStore("form-builder-store", () => {
}
// In List View is not allowed for some fieldtypes
if (df.in_list_view && in_list(not_allowed_in_list_view, df.fieldtype)) {
if (df.in_list_view && not_allowed_in_list_view.includes(df.fieldtype)) {
error_message = __(
"'In List View' is not allowed for field {0} of type {1}",
get_field_data(df)
@ -195,7 +195,7 @@ export const useStore = defineStore("form-builder-store", () => {
}
// In Global Search is not allowed for no_value_type fields
if (df.in_global_search && in_list(frappe.model.no_value_type, df.fieldtype)) {
if (df.in_global_search && frappe.model.no_value_type.includes(df.fieldtype)) {
error_message = __(
"'In Global Search' is not allowed for field {0} of type {1}",
get_field_data(df)

View file

@ -119,7 +119,7 @@ export async function get_table_columns(df, child_doctype) {
1,
]);
for (let tf of table_fields) {
if (!in_list(frappe.model.layout_fields, tf.fieldtype) && tf.in_list_view && tf.label) {
if (!frappe.model.layout_fields.includes(tf.fieldtype) && tf.in_list_view && tf.label) {
let colsize;
if (tf.columns) {
@ -281,7 +281,7 @@ export function scrub_field_names(fields) {
if (d.fieldname.endsWith("?")) {
d.fieldname = d.fieldname.slice(0, -1);
}
if (in_list(frappe.model.restricted_fields, d.fieldname)) {
if (frappe.model.restricted_fields.includes(d.fieldname)) {
d.fieldname = d.fieldname + "1";
}
if (d.fieldtype == "Section Break") {
@ -298,7 +298,7 @@ export function scrub_field_names(fields) {
frappe.utils.get_random(4);
}
} else {
if (in_list(frappe.model.restricted_fields, d.fieldname)) {
if (frappe.model.restricted_fields.includes(d.fieldname)) {
frappe.throw(__("Fieldname {0} is restricted", [d.fieldname]));
}
}

View file

@ -473,7 +473,7 @@ function check_restrictions(file) {
return is_correct_type && valid_file_size;
}
function upload_files() {
function upload_files(dialog) {
if (show_file_browser.value) {
return upload_via_file_browser();
}
@ -483,6 +483,14 @@ function upload_files() {
if (props.as_dataurl) {
return return_as_dataurl();
}
if (!files.value.length) {
frappe.msgprint(__("Please select a file first."));
return Promise.reject();
}
dialog?.get_primary_btn().prop("disabled", true);
dialog?.get_secondary_btn().prop("disabled", true);
return frappe.run_serially(files.value.map((file, i) => () => upload_file(file, i)));
}
function upload_via_file_browser() {

View file

@ -113,9 +113,7 @@ class FileUploader {
}
upload_files() {
this.dialog && this.dialog.get_primary_btn().prop("disabled", true);
this.dialog && this.dialog.get_secondary_btn().prop("disabled", true);
return this.uploader.upload_files();
return this.uploader.upload_files(this.dialog);
}
make_dialog(title) {

View file

@ -85,33 +85,15 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui
};
}
init_option_cache() {
if (!this.$input.cache) {
this.$input.cache = {};
}
if (!this.$input.cache[this.doctype]) {
this.$input.cache[this.doctype] = {};
}
if (!this.$input.cache[this.doctype][this.df.fieldname]) {
this.$input.cache[this.doctype][this.df.fieldname] = {};
}
}
setup_awesomplete() {
this.awesomplete = new Awesomplete(this.input, this.get_awesomplete_settings());
$(this.input_area).find(".awesomplete ul").css("min-width", "100%");
this.init_option_cache();
this.$input.on(
"input",
frappe.utils.debounce((e) => {
const cached_options =
this.$input.cache[this.doctype][this.df.fieldname][e.target.value];
if (cached_options && cached_options.length) {
this.set_data(cached_options);
} else if (this.get_query || this.df.get_query) {
if (this.get_query || this.df.get_query) {
this.execute_query_if_exists(e.target.value);
} else {
this.awesomplete.list = this.get_data();
@ -245,7 +227,6 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui
if (!this.$input.is(":focus")) {
return;
}
this.$input.cache[this.doctype][this.df.fieldname][term] = message;
this.set_data(message);
},
});

View file

@ -87,7 +87,7 @@ frappe.ui.form.Control = class BaseControl {
if (
status === "Read" &&
is_null(value) &&
!in_list(["HTML", "Image", "Button"], this.df.fieldtype)
!["HTML", "Image", "Button"].includes(this.df.fieldtype)
)
status = "Read";
@ -115,7 +115,7 @@ frappe.ui.form.Control = class BaseControl {
let value = frappe.model.get_value(this.doctype, this.docname, this.df.fieldname);
if (in_list(["Date", "Datetime"], this.df.fieldtype) && value) {
if (["Date", "Datetime"].includes(this.df.fieldtype) && value) {
value = frappe.datetime.str_to_user(value);
}
@ -127,7 +127,7 @@ frappe.ui.form.Control = class BaseControl {
status === "Read" &&
!this.only_input &&
is_null(value) &&
!in_list(["HTML", "Image", "Button", "Geolocation"], this.df.fieldtype)
!["HTML", "Image", "Button", "Geolocation"].includes(this.df.fieldtype)
) {
if (explain) console.log("By Hide Read-only, null fields: None");
status = "None";

View file

@ -138,7 +138,7 @@ frappe.ui.form.ControlInput = class ControlInput extends frappe.ui.form.Control
set_disp_area(value) {
if (
in_list(["Currency", "Int", "Float"], this.df.fieldtype) &&
["Currency", "Int", "Float"].includes(this.df.fieldtype) &&
(this.value === 0 || value === 0)
) {
// to set the 0 value in readonly for currency, int, float field
@ -172,7 +172,7 @@ frappe.ui.form.ControlInput = class ControlInput extends frappe.ui.form.Control
if (
!this.df.label ||
!this.df?.documentation_url ||
in_list(unsupported_fieldtypes, this.df.fieldtype)
unsupported_fieldtypes.includes(this.df.fieldtype)
)
return;

View file

@ -161,6 +161,7 @@ frappe.ui.form.ControlCode = class ControlCode extends frappe.ui.form.ControlTex
Golang: "ace/mode/golang",
Go: "ace/mode/golang",
Jinja: "ace/mode/django",
SQL: "ace/mode/sql",
};
const language = this.df.options;

View file

@ -215,8 +215,7 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp
}
set_input_attributes() {
if (
in_list(
["Data", "Link", "Dynamic Link", "Password", "Select", "Read Only"],
["Data", "Link", "Dynamic Link", "Password", "Select", "Read Only"].includes(
this.df.fieldtype
)
) {

View file

@ -16,7 +16,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co
}
get_start_date() {
this.value = this.value == null ? undefined : this.value;
this.value = this.value == null || this.value == "" ? undefined : this.value;
let value = frappe.datetime.convert_to_user_tz(this.value);
return frappe.datetime.str_to_obj(value);
}

View file

@ -87,10 +87,10 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
return this.is_translatable() ? __(value) : value;
}
is_translatable() {
return in_list(frappe.boot?.translated_doctypes || [], this.get_options());
return frappe.boot?.translated_doctypes || [].includes(this.get_options());
}
is_title_link() {
return in_list(frappe.boot?.link_title_doctypes || [], this.get_options());
return frappe.boot?.link_title_doctypes || [].includes(this.get_options());
}
async set_link_title(value) {
const doctype = this.get_options();

View file

@ -75,13 +75,15 @@ frappe.ui.form.ControlMultiCheck = class ControlMultiCheck extends frappe.ui.for
make_checkboxes() {
this.$load_state.hide();
this.$checkbox_area.empty();
this.options.forEach((option) => {
let checkbox = this.get_checkbox_element(option).appendTo(this.$checkbox_area);
if (option.danger) {
checkbox.find(".label-area").addClass("text-danger");
}
option.$checkbox = checkbox;
});
this.options
.sort((a, b) => cstr(a.label).localeCompare(cstr(b.label)))
.forEach((option) => {
let checkbox = this.get_checkbox_element(option).appendTo(this.$checkbox_area);
if (option.danger) {
checkbox.find(".label-area").addClass("text-danger");
}
option.$checkbox = checkbox;
});
if (this.df.select_all) {
this.setup_select_all();
}
@ -152,7 +154,7 @@ frappe.ui.form.ControlMultiCheck = class ControlMultiCheck extends frappe.ui.for
<div class="checkbox unit-checkbox">
<label title="${option.description || ""}">
<input type="checkbox" data-unit="${option.value}"></input>
<span class="label-area" data-unit="${option.value}">${__(option.label)}</span>
<span class="label-area" data-unit="${option.value}">${option.label}</span>
</label>
</div>
`);

View file

@ -1,4 +1,4 @@
frappe.ui.form.ControlRating = class ControlRating extends frappe.ui.form.ControlInt {
frappe.ui.form.ControlRating = class ControlRating extends frappe.ui.form.ControlFloat {
make_input() {
super.make_input();
let stars = "";

View file

@ -166,7 +166,7 @@ frappe.ui.form.ControlTableMultiSelect = class ControlTableMultiSelect extends (
let me = this;
awesomplete.filter = function (item) {
if (in_list(me._rows_list, item.value)) {
if (me._rows_list.includes(item.value)) {
return false;
}

View file

@ -295,11 +295,11 @@ class FormTimeline extends BaseTimeline {
set_communication_doc_status(doc) {
let indicator_color = "red";
if (in_list(["Sent", "Clicked"], doc.delivery_status)) {
if (["Sent", "Clicked"].includes(doc.delivery_status)) {
indicator_color = "green";
} else if (["Sending", "Scheduled"].includes(doc.delivery_status)) {
indicator_color = "orange";
} else if (in_list(["Opened", "Read"], doc.delivery_status)) {
} else if (["Opened", "Read"].includes(doc.delivery_status)) {
indicator_color = "blue";
} else if (doc.delivery_status == "Error") {
indicator_color = "red";

View file

@ -1412,7 +1412,7 @@ frappe.ui.form.Form = class FrappeForm {
is_form_builder() {
return (
in_list(["DocType", "Customize Form"], this.doctype) &&
["DocType", "Customize Form"].includes(this.doctype) &&
this.get_active_tab().label == "Form"
);
}

View file

@ -158,7 +158,7 @@ export default class Grid {
if (
!this.df.label ||
!this.df?.documentation_url ||
in_list(unsupported_fieldtypes, this.df.fieldtype)
unsupported_fieldtypes.includes(this.df.fieldtype)
)
return;
@ -685,7 +685,7 @@ export default class Grid {
get_modal_data() {
return this.df.get_data
? this.df.get_data().filter((data) => {
if (!this.deleted_docs || !in_list(this.deleted_docs, data.name)) {
if (!this.deleted_docs || !this.deleted_docs.includes(data.name)) {
return data;
}
})
@ -940,7 +940,7 @@ export default class Grid {
!df.hidden &&
(this.editable_fields || df.in_list_view) &&
((this.frm && this.frm.get_perm(df.permlevel, "read")) || !this.frm) &&
!in_list(frappe.model.layout_fields, df.fieldtype)
!frappe.model.layout_fields.includes(df.fieldtype)
) {
if (df.columns) {
df.colsize = df.columns;

View file

@ -429,10 +429,10 @@ export default class GridRow {
$(`
<div class='form-group'>
<div class='row' style='margin:0px; margin-bottom:10px;'>
<div class='col-md-8'>
<div class='col-6 col-md-8'>
${__("Fieldname").bold()}
</div>
<div class='col-md-4' style='padding-left:5px;'>
<div class='col-6 col-md-4' style='padding-left:5px;'>
${__("Column Width").bold()}
</div>
</div>
@ -500,7 +500,7 @@ export default class GridRow {
fields.push({
label: column.label,
value: column.fieldname,
checked: selected_fields ? in_list(selected_fields, column.fieldname) : false,
checked: selected_fields ? selected_fields.includes(column.fieldname) : false,
});
}
});
@ -522,13 +522,13 @@ export default class GridRow {
data-label='${docfield.label}' data-type='${docfield.fieldtype}'>
<div class='row'>
<div class='col-md-1' style='padding-top: 4px;'>
<div class='col-1' style='padding-top: 4px;'>
<a style='cursor: grabbing;'>${frappe.utils.icon("drag", "xs")}</a>
</div>
<div class='col-md-8' style='padding-right:0px; padding-top: 5px;'>
<div class='col-6 col-md-8' style='padding-right:0px; padding-top: 5px;'>
${__(docfield.label)}
</div>
<div class='col-md-2' style='padding-left:0px; padding-top: 2px; margin-top:-2px;' title='${__(
<div class='col-3 col-md-2' style='padding-left:0px; padding-top: 2px; margin-top:-2px;' title='${__(
"Columns"
)}'>
<input class='form-control column-width my-1 input-xs text-right'
@ -536,7 +536,7 @@ export default class GridRow {
value='${docfield.columns || cint(d.columns)}'
data-fieldname='${docfield.fieldname}' style='background-color: var(--modal-bg); display: inline'>
</div>
<div class='col-md-1' style='padding-top: 3px;'>
<div class='col-1' style='padding-top: 3px;'>
<a class='text-muted remove-field' data-fieldname='${docfield.fieldname}'>
<i class='fa fa-trash-o' aria-hidden='true'></i>
</a>
@ -1135,8 +1135,8 @@ export default class GridRow {
let ignore_fieldtypes = ["Text", "Small Text", "Code", "Text Editor", "HTML Editor"];
if (field.$input) {
field.$input.on("keydown", function (e) {
var { TAB, UP: UP_ARROW, DOWN: DOWN_ARROW } = frappe.ui.keyCode;
if (!in_list([TAB, UP_ARROW, DOWN_ARROW], e.which)) {
var { ESCAPE, TAB, UP: UP_ARROW, DOWN: DOWN_ARROW } = frappe.ui.keyCode;
if (![TAB, UP_ARROW, DOWN_ARROW, ESCAPE].includes(e.which)) {
return;
}
@ -1145,7 +1145,7 @@ export default class GridRow {
var fieldtype = $(this).attr("data-fieldtype");
let ctrl_key = e.metaKey || e.ctrlKey;
if (!in_list(ignore_fieldtypes, fieldtype) && ctrl_key && e.which !== TAB) {
if (!ignore_fieldtypes.includes(fieldtype) && ctrl_key && e.which !== TAB) {
me.add_new_row_using_keys(e);
return;
}
@ -1156,7 +1156,7 @@ export default class GridRow {
}
var move_up_down = function (base) {
if (in_list(ignore_fieldtypes, fieldtype) && !e.altKey) {
if (ignore_fieldtypes.includes(fieldtype) && !e.altKey) {
return false;
}
if (field.autocomplete_open) {
@ -1171,6 +1171,14 @@ export default class GridRow {
return true;
};
// ESC
if (e.which === ESCAPE && !e.shiftKey) {
if (me.doc.__unedited) {
me.grid.grid_rows[me.doc.idx - 1].remove();
}
return false;
}
// TAB
if (e.which === TAB && !e.shiftKey) {
var last_column = me.wrapper.find(":input:enabled:last").get(0);
@ -1441,8 +1449,8 @@ export default class GridRow {
!df.hidden &&
df.in_list_view &&
me.grid.frm.get_perm(df.permlevel, "read") &&
!in_list(frappe.model.layout_fields, df.fieldtype) &&
!in_list(blacklist, df.fieldname);
!frappe.model.layout_fields.includes(df.fieldtype) &&
!blacklist.includes(df.fieldname);
return visible ? df : null;
});

View file

@ -134,7 +134,7 @@ export default class GridRowForm {
var first = me.form_area.find("input:first");
if (
first.length &&
!in_list(["Date", "Datetime", "Time"], first.attr("data-fieldtype"))
!["Date", "Datetime", "Time"].includes(first.attr("data-fieldtype"))
) {
try {
first.get(0).focus();

View file

@ -621,7 +621,7 @@ frappe.ui.form.Layout = class Layout {
// show grid row (if exists)
field.grid.grid_rows[0].show_form();
return true;
} else if (!in_list(frappe.model.no_value_type, field.df.fieldtype)) {
} else if (!frappe.model.no_value_type.includes(field.df.fieldtype)) {
this.set_focus(field);
return true;
}

View file

@ -372,7 +372,7 @@ frappe.ui.form.Toolbar = class Toolbar {
}
// duplicate
if (in_list(frappe.boot.user.can_create, me.frm.doctype) && !me.frm.meta.allow_copy) {
if (frappe.boot.user.can_create.includes(me.frm.doctype) && !me.frm.meta.allow_copy) {
this.page.add_menu_item(
__("Duplicate"),
function () {

View file

@ -114,13 +114,13 @@ export default class ListSettings {
data-label="${me.fields[idx].label}" data-type="${me.fields[idx].type}">
<div class="row">
<div class="col-md-1">
<div class="col-1">
${frappe.utils.icon("drag", "xs", "", "", "sortable-handle " + show_sortable_handle)}
</div>
<div class="col-md-10" style="padding-left:0px;">
<div class="col-10" style="padding-left:0px;">
${me.fields[idx].label}
</div>
<div class="col-md-1 ${can_remove}">
<div class="col-1 ${can_remove}">
<a class="text-muted remove-field" data-fieldname="${me.fields[idx].fieldname}">
${frappe.utils.icon("delete", "xs")}
</a>
@ -316,7 +316,7 @@ export default class ListSettings {
meta.fields.forEach((field) => {
if (
field.in_list_view &&
!in_list(frappe.model.no_value_type, field.fieldtype) &&
!frappe.model.no_value_type.includes(field.fieldtype) &&
me.subject_field.fieldname != field.fieldname
) {
me.fields.push({
@ -363,11 +363,11 @@ export default class ListSettings {
let multiselect_fields = [];
meta.fields.forEach((field) => {
if (!in_list(frappe.model.no_value_type, field.fieldtype)) {
if (!frappe.model.no_value_type.includes(field.fieldtype)) {
multiselect_fields.push({
label: field.label,
value: field.fieldname,
checked: in_list(fields, field.fieldname),
checked: fields.includes(field.fieldname),
});
}
});
@ -384,7 +384,7 @@ export default class ListSettings {
}
existing_fields.forEach((column) => {
if (!in_list(new_fields, column)) {
if (!new_fields.includes(column)) {
removed_fields.push(column);
}
});

View file

@ -478,7 +478,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
"/assets/frappe/images/ui-states/list-empty-state.svg";
const new_button = this.can_create
? `<p><button class="btn btn-primary btn-sm btn-new-doc hidden-xs">
? `<p><button class="btn btn-default btn-sm btn-new-doc hidden-xs">
${new_button_label}
</button> <button class="btn btn-primary btn-new-doc visible-xs">
${__("Create New", null, "Create a new document from list view")}
@ -755,7 +755,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
: value;
let translated_doctypes = frappe.boot?.translated_doctypes || [];
if (in_list(translated_doctypes, df.options)) {
if (translated_doctypes.includes(df.options)) {
value_display = __(value_display);
}
@ -2016,9 +2016,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
parse_filters_from_route_options() {
const filters = [];
for (let field in frappe.route_options) {
let params = new URLSearchParams(window.location.search);
if (!params.toString() && frappe.route_options) {
params = new Map(Object.entries(frappe.route_options));
}
params.forEach((value, field) => {
let doctype = null;
let value = frappe.route_options[field];
let value_array;
if ($.isArray(value) && value[0].startsWith("[") && value[0].endsWith("]")) {
@ -2060,7 +2064,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
filters.push([doctype, field, "=", value]);
}
}
}
});
return filters;
}

View file

@ -62,7 +62,7 @@ $.extend(frappe.model, {
var df = frappe.meta.has_field(doctype, fieldname);
if (
df &&
in_list(["Link", "Data", "Select", "Dynamic Link"], df.fieldtype) &&
["Link", "Data", "Select", "Dynamic Link"].includes(df.fieldtype) &&
!df.no_copy
) {
doc[fieldname] = value;
@ -84,30 +84,39 @@ $.extend(frappe.model, {
},
set_default_values: function (doc, parent_doc) {
var doctype = doc.doctype;
var docfields = frappe.meta.get_docfields(doctype);
var updated = [];
for (var fid = 0; fid < docfields.length; fid++) {
var f = docfields[fid];
if (!in_list(frappe.model.no_value_type, f.fieldtype) && doc[f.fieldname] == null) {
if (f.no_default) continue;
var v = frappe.model.get_default_value(f, doc, parent_doc);
if (v) {
if (in_list(["Int", "Check"], f.fieldtype)) v = cint(v);
else if (in_list(["Currency", "Float"], f.fieldtype)) v = flt(v);
let doctype = doc.doctype;
let docfields = frappe.meta.get_docfields(doctype);
let updated = [];
doc[f.fieldname] = v;
updated.push(f.fieldname);
} else if (
f.fieldtype == "Select" &&
f.options &&
typeof f.options === "string" &&
!in_list(["[Select]", "Loading..."], f.options)
) {
doc[f.fieldname] = f.options.split("\n")[0];
}
// Table types should be initialized
let fieldtypes_without_default = frappe.model.no_value_type.filter(
(fieldtype) => !frappe.model.table_fields.includes(fieldtype)
);
docfields.forEach((f) => {
if (
fieldtypes_without_default.includes(f.fieldtype) ||
doc[f.fieldname] != null ||
f.no_default
) {
return;
}
}
let v = frappe.model.get_default_value(f, doc, parent_doc);
if (v) {
if (["Int", "Check"].includes(f.fieldtype)) v = cint(v);
else if (["Currency", "Float"].includes(f.fieldtype)) v = flt(v);
doc[f.fieldname] = v;
updated.push(f.fieldname);
} else if (
f.fieldtype == "Select" &&
f.options &&
typeof f.options === "string" &&
!["[Select]", "Loading..."].includes(f.options)
) {
doc[f.fieldname] = f.options.split("\n")[0];
}
});
return updated;
},
@ -219,6 +228,10 @@ $.extend(frappe.model, {
value = frappe.datetime.now_time();
}
if (frappe.model.table_fields.includes(df.fieldtype)) {
value = [];
}
// set it here so we know it was set as a default
df.__default_value = value;
@ -280,7 +293,7 @@ $.extend(frappe.model, {
if (
df &&
key.substr(0, 2) != "__" &&
!in_list(no_copy_list, key) &&
!no_copy_list.includes(key) &&
!(df && !from_amend && cint(df.no_copy) == 1)
) {
var value = doc[key] || [];

View file

@ -154,7 +154,7 @@ $.extend(frappe.meta, {
get_doctype_for_field: function (doctype, key) {
var out = null;
if (in_list(frappe.model.std_fields_list, key)) {
if (frappe.model.std_fields_list.includes(key)) {
// standard
out = doctype;
} else if (frappe.meta.has_field(doctype, key)) {
@ -164,7 +164,7 @@ $.extend(frappe.meta, {
frappe.meta.get_table_fields(doctype).every(function (d) {
if (
frappe.meta.has_field(d.options, key) ||
in_list(frappe.model.child_table_field_list, key)
frappe.model.child_table_field_list.includes(key)
) {
out = d.options;
return false;
@ -264,7 +264,7 @@ $.extend(frappe.meta, {
});
$.each(print_formats, function (i, d) {
if (
!in_list(print_format_list, d.name) &&
!print_format_list.includes(d.name) &&
d.print_format_type !== "JS" &&
(cint(enable_raw_printing) || !d.raw_printing)
) {

Some files were not shown because too many files have changed in this diff Show more