Merge branch 'develop' into po-translation
This commit is contained in:
commit
9620a3c596
175 changed files with 1692 additions and 1154 deletions
2
.github/workflows/initiate_release.yml
vendored
2
.github/workflows/initiate_release.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: ["13", "14", "15"]
|
||||
version: ["14", "15"]
|
||||
|
||||
steps:
|
||||
- uses: octokit/request-action@v2.x
|
||||
|
|
|
|||
20
.github/workflows/server-tests.yml
vendored
20
.github/workflows/server-tests.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
17
.github/workflows/ui-tests.yml
vendored
17
.github/workflows/ui-tests.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 }, () => {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"]:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -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": []
|
||||
}
|
||||
|
|
@ -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):
|
||||
...
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 & 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"
|
||||
}
|
||||
|
|
@ -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 & 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",
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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" &&
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }}
|
|||
</ul>
|
||||
</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",
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -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 = "";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] || [];
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue