Merge branch 'develop' into whats-new
This commit is contained in:
commit
dc0d76a6db
155 changed files with 1129 additions and 1264 deletions
65
.eslintrc
65
.eslintrc
|
|
@ -2,65 +2,32 @@
|
|||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
"es2022": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 11,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
"tab",
|
||||
{ "SwitchCase": 1 }
|
||||
],
|
||||
"brace-style": [
|
||||
"error",
|
||||
"1tbs"
|
||||
],
|
||||
"space-unary-ops": [
|
||||
"error",
|
||||
{ "words": true }
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"off"
|
||||
],
|
||||
"semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"camelcase": [
|
||||
"off"
|
||||
],
|
||||
"no-unused-vars": [
|
||||
"warn"
|
||||
],
|
||||
"no-redeclare": [
|
||||
"warn"
|
||||
],
|
||||
"no-console": [
|
||||
"warn"
|
||||
],
|
||||
"no-extra-boolean-cast": [
|
||||
"off"
|
||||
],
|
||||
"no-control-regex": [
|
||||
"off"
|
||||
],
|
||||
"space-before-blocks": "warn",
|
||||
"keyword-spacing": "warn",
|
||||
"comma-spacing": "warn",
|
||||
"key-spacing": "warn",
|
||||
"indent": "off",
|
||||
"brace-style": "off",
|
||||
"no-mixed-spaces-and-tabs": "off",
|
||||
"no-useless-escape": "off",
|
||||
"space-unary-ops": ["error", { "words": true }],
|
||||
"linebreak-style": "off",
|
||||
"quotes": ["off"],
|
||||
"semi": "off",
|
||||
"camelcase": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-console": ["warn"],
|
||||
"no-extra-boolean-cast": ["off"],
|
||||
"no-control-regex": ["off"],
|
||||
},
|
||||
"root": true,
|
||||
"globals": {
|
||||
"frappe": true,
|
||||
"Vue": true,
|
||||
"SetVueGlobals": true,
|
||||
"__": true,
|
||||
"repl": true,
|
||||
"Class": true,
|
||||
|
|
@ -76,8 +43,10 @@
|
|||
"is_null": true,
|
||||
"in_list": true,
|
||||
"has_common": true,
|
||||
"posthog": true,
|
||||
"has_words": true,
|
||||
"validate_email": true,
|
||||
"open_web_template_values_editor": true,
|
||||
"validate_name": true,
|
||||
"validate_phone": true,
|
||||
"validate_url": true,
|
||||
|
|
|
|||
3
.github/workflows/linters.yml
vendored
3
.github/workflows/linters.yml
vendored
|
|
@ -62,6 +62,7 @@ jobs:
|
|||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
cache: pip
|
||||
- uses: pre-commit/action@v3.0.0
|
||||
|
||||
- name: Download Semgrep rules
|
||||
|
|
@ -69,7 +70,7 @@ jobs:
|
|||
|
||||
- name: Run Semgrep rules
|
||||
run: |
|
||||
pip install semgrep==0.97.0
|
||||
pip install semgrep
|
||||
semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
|
||||
|
||||
deps-vulnerable-check:
|
||||
|
|
|
|||
10
.github/workflows/release_notes.yml
vendored
10
.github/workflows/release_notes.yml
vendored
|
|
@ -29,10 +29,14 @@ jobs:
|
|||
steps:
|
||||
- name: Update notes
|
||||
run: |
|
||||
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/frappe/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' )
|
||||
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/frappe/releases/generate-notes -f tag_name=$RELEASE_TAG \
|
||||
| jq -r '.body' \
|
||||
| sed -E '/^\* (chore|ci|test|docs|style)/d' \
|
||||
| sed -E 's/by @mergify //'
|
||||
)
|
||||
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/frappe/releases/tags/$RELEASE_TAG | jq -r '.id')
|
||||
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/frappe/releases/$RELEASE_ID -f body=$NEW_NOTES
|
||||
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/frappe/releases/$RELEASE_ID -f body="$NEW_NOTES"
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ repos:
|
|||
- id: debug-statements
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.34.0
|
||||
rev: v3.9.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ['--py310-plus']
|
||||
|
|
@ -48,13 +48,31 @@ repos:
|
|||
)$
|
||||
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: v8.44.0
|
||||
hooks:
|
||||
- id: eslint
|
||||
types_or: [javascript]
|
||||
args: ['--quiet']
|
||||
# Ignore any files that might contain jinja / bundles
|
||||
exclude: |
|
||||
(?x)^(
|
||||
frappe/public/dist/.*|
|
||||
cypress/.*|
|
||||
.*node_modules.*|
|
||||
.*boilerplate.*|
|
||||
frappe/www/website_script.js|
|
||||
frappe/templates/includes/.*|
|
||||
frappe/public/js/lib/.*
|
||||
)$
|
||||
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies: ['flake8-bugbear',]
|
||||
|
|
|
|||
|
|
@ -9,38 +9,23 @@ context("Form Builder", () => {
|
|||
|
||||
it("Open Form Builder for Web Form Doctype/Customize Form", () => {
|
||||
// doctype
|
||||
cy.visit("/app/form-builder/Web Form");
|
||||
cy.visit("/app/doctype/Web Form");
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
cy.get(".form-builder-container").should("exist");
|
||||
|
||||
// customize form
|
||||
cy.visit("/app/form-builder/Web Form/customize");
|
||||
cy.visit("/app/customize-form?doc_type=Web%20Form");
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
cy.get(".form-builder-container").should("exist");
|
||||
});
|
||||
|
||||
it("Change Doctype using page title dialog", () => {
|
||||
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
|
||||
|
||||
cy.visit(`/app/form-builder/Web Form`);
|
||||
cy.get(".form-builder-container").should("exist");
|
||||
|
||||
cy.get(".page-title").click();
|
||||
|
||||
cy.get(".frappe-control[data-fieldname='doctype'] input").click().as("input");
|
||||
cy.get("@input").type("{rightArrow}Web Form Field", { delay: 200 });
|
||||
cy.wait("@search_link");
|
||||
cy.get("@input").type("{enter}").blur();
|
||||
|
||||
cy.click_modal_primary_button("Edit");
|
||||
|
||||
cy.get(".page-title .title-text").should("have.text", "Web Form Field");
|
||||
});
|
||||
|
||||
it("Save without change, check form dirty and reset changes", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
it("Save without change, check form dirty", () => {
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
// Save without change
|
||||
cy.click_doc_primary_button("Save");
|
||||
cy.get(".desk-alert.orange .alert-message").should("have.text", "No changes to save");
|
||||
cy.get(".desk-alert.orange .alert-message").should("have.text", "No changes in document");
|
||||
|
||||
// Check form dirty
|
||||
cy.get(".tab-content.active .section-columns-container:first .column:first .field:first")
|
||||
|
|
@ -48,14 +33,11 @@ context("Form Builder", () => {
|
|||
.dblclick()
|
||||
.type("Dirty");
|
||||
cy.get(".title-area .indicator-pill.orange").should("have.text", "Not Saved");
|
||||
|
||||
// Reset changes
|
||||
cy.get(".page-actions .custom-actions .btn").contains("Reset Changes").click();
|
||||
cy.get(".title-area .indicator-pill.orange").should("not.exist");
|
||||
});
|
||||
|
||||
it("Add empty section and save", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
let first_section = ".tab-content.active .form-section-container:first";
|
||||
|
||||
|
|
@ -71,7 +53,8 @@ context("Form Builder", () => {
|
|||
it("Add Table field and check if columns are rendered", () => {
|
||||
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
|
||||
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
let first_field =
|
||||
".tab-content.active .section-columns-container:first .column:first .field:first";
|
||||
|
|
@ -126,20 +109,23 @@ context("Form Builder", () => {
|
|||
});
|
||||
|
||||
it("Drag Field/Column/Section & Tab", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
let first_column = ".tab-content.active .section-columns-container:first .column:first";
|
||||
let first_field = first_column + " .field:first";
|
||||
let label = "div[title='Double click to edit label'] span:first";
|
||||
|
||||
cy.get(".tab-header .tabs .tab:first").click();
|
||||
|
||||
// drag first tab to second position
|
||||
cy.get(".tabs .tab:first").drag(".tabs .tab:nth-child(2)", {
|
||||
cy.get(".tab-header .tabs .tab:first").drag(".tab-header .tabs .tab:nth-child(2)", {
|
||||
target: { x: 10, y: 10 },
|
||||
force: true,
|
||||
});
|
||||
cy.get(".tabs .tab:first").find(label).should("have.text", "Tab 2");
|
||||
cy.get(".tab-header .tabs .tab:first").find(label).should("have.text", "Tab 2");
|
||||
|
||||
cy.get(".tabs .tab:first").click();
|
||||
cy.get(".tab-header .tabs .tab:first").click();
|
||||
cy.get(".sidebar-container .tab:first").click();
|
||||
|
||||
// drag check field to first column
|
||||
|
|
@ -151,7 +137,7 @@ context("Form Builder", () => {
|
|||
cy.get(first_field)
|
||||
.find("div[title='Double click to edit label']")
|
||||
.dblclick()
|
||||
.type("Test Check{enter}");
|
||||
.type("Test Check");
|
||||
cy.get(first_field).find(label).should("have.text", "Test Check");
|
||||
|
||||
// drag the first field to second position
|
||||
|
|
@ -184,13 +170,14 @@ context("Form Builder", () => {
|
|||
});
|
||||
|
||||
it("Add New Tab/Section/Column to Form", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
let first_section = ".tab-content.active .form-section-container:first";
|
||||
|
||||
// add new tab
|
||||
cy.get(".tab-header").realHover().find(".tab-actions .new-tab-btn").click();
|
||||
cy.get(".tabs .tab").should("have.length", 3);
|
||||
cy.get(".tab-header .tabs .tab").should("have.length", 3);
|
||||
|
||||
// add new section
|
||||
cy.get(first_section).click(15, 10);
|
||||
|
|
@ -218,11 +205,12 @@ context("Form Builder", () => {
|
|||
|
||||
// remove tab
|
||||
cy.get(".tab-header").realHover().find(".tab-actions .remove-tab-btn").click();
|
||||
cy.get(".tabs .tab").should("have.length", 2);
|
||||
cy.get(".tab-header .tabs .tab").should("have.length", 2);
|
||||
});
|
||||
|
||||
it("Update Title field Label to New Title through Customize Form", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
let first_field =
|
||||
".tab-content.active .section-columns-container:first .column:first .field:first";
|
||||
|
|
@ -239,7 +227,8 @@ context("Form Builder", () => {
|
|||
});
|
||||
|
||||
it("Validate Duplicate Name & reqd + hidden without default logic", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
let first_field =
|
||||
".tab-content.active .section-columns-container:first .column:first .field:first";
|
||||
|
|
@ -275,10 +264,11 @@ context("Form Builder", () => {
|
|||
});
|
||||
|
||||
it("Undo/Redo", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
// click on second tab
|
||||
cy.get(".tabs .tab:last").click();
|
||||
cy.get(".tab-header .tabs .tab:last").click();
|
||||
|
||||
let first_column = ".tab-content.active .section-columns-container:first .column:first";
|
||||
let first_field = first_column + " .field:first";
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ context("Grid Configuration", () => {
|
|||
cy.visit("/app/doctype/User");
|
||||
});
|
||||
it("Set user wise grid settings", () => {
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
cy.get('.form-section[data-fieldname="fields_section"]').click();
|
||||
cy.wait(100);
|
||||
cy.get('.frappe-control[data-fieldname="fields"]').as("table");
|
||||
cy.get("@table").find(".icon-sm").click();
|
||||
|
|
|
|||
|
|
@ -27,60 +27,70 @@ context("Sidebar", () => {
|
|||
});
|
||||
|
||||
it("Verify attachment visibility config", () => {
|
||||
verify_attachment_visibility("doctype/Blog Post", true);
|
||||
cy.call("frappe.tests.ui_test_helpers.create_todo", {
|
||||
description: "Sidebar Attachment ToDo",
|
||||
}).then((todo) => {
|
||||
verify_attachment_visibility(`todo/${todo.message.name}`, true);
|
||||
});
|
||||
verify_attachment_visibility("blog-post/test-blog-attachment-post", false);
|
||||
});
|
||||
|
||||
it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => {
|
||||
cy.visit("/app/doctype");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
cy.call("frappe.tests.ui_test_helpers.create_todo", {
|
||||
description: "Sidebar Attachment ToDo",
|
||||
}).then((todo) => {
|
||||
let todo_name = todo.message.name;
|
||||
cy.visit("/app/todo");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
|
||||
//To check if no filter is available in "Assigned To" dropdown
|
||||
cy.get(".empty-state").should("contain", "No filters found");
|
||||
//To check if no filter is available in "Assigned To" dropdown
|
||||
cy.get(".empty-state").should("contain", "No filters found");
|
||||
|
||||
//Assigning a doctype to a user
|
||||
cy.visit("/app/doctype/ToDo");
|
||||
cy.get(".form-assignments > .flex > .text-muted").click();
|
||||
cy.get_field("assign_to_me", "Check").click();
|
||||
cy.get(".modal-footer > .standard-actions > .btn-primary").click();
|
||||
cy.visit("/app/doctype");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
//Assigning a doctype to a user
|
||||
cy.visit(`/app/todo/${todo_name}`);
|
||||
cy.get(".form-assignments > .flex > .text-muted").click();
|
||||
cy.get_field("assign_to_me", "Check").click();
|
||||
cy.get(".modal-footer > .standard-actions > .btn-primary").click();
|
||||
cy.visit("/app/todo");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
|
||||
//To check if filter is added in "Assigned To" dropdown after assignment
|
||||
cy.get(".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item").should(
|
||||
"contain",
|
||||
"1"
|
||||
);
|
||||
//To check if filter is added in "Assigned To" dropdown after assignment
|
||||
cy.get(
|
||||
".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item"
|
||||
).should("contain", "1");
|
||||
|
||||
//To check if there is no filter added to the listview
|
||||
cy.get(".filter-button").should("contain", "Filter");
|
||||
//To check if there is no filter added to the listview
|
||||
cy.get(".filter-button").should("contain", "Filter");
|
||||
|
||||
//To add a filter to display data into the listview
|
||||
cy.get(".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item").click();
|
||||
//To add a filter to display data into the listview
|
||||
cy.get(
|
||||
".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item"
|
||||
).click();
|
||||
|
||||
//To check if filter is applied
|
||||
cy.click_filter_button().should("contain", "1 filter");
|
||||
cy.get(".fieldname-select-area > .awesomplete > .form-control").should(
|
||||
"have.value",
|
||||
"Assigned To"
|
||||
);
|
||||
cy.get(".condition").should("have.value", "like");
|
||||
cy.get(".filter-field > .form-group > .input-with-feedback").should(
|
||||
"have.value",
|
||||
`%${cy.config("testUser")}%`
|
||||
);
|
||||
cy.click_filter_button();
|
||||
//To check if filter is applied
|
||||
cy.click_filter_button().should("contain", "1 filter");
|
||||
cy.get(".fieldname-select-area > .awesomplete > .form-control").should(
|
||||
"have.value",
|
||||
"Assigned To"
|
||||
);
|
||||
cy.get(".condition").should("have.value", "like");
|
||||
cy.get(".filter-field > .form-group > .input-with-feedback").should(
|
||||
"have.value",
|
||||
`%${cy.config("testUser")}%`
|
||||
);
|
||||
cy.click_filter_button();
|
||||
|
||||
//To remove the applied filter
|
||||
cy.clear_filters();
|
||||
//To remove the applied filter
|
||||
cy.clear_filters();
|
||||
|
||||
//To remove the assignment
|
||||
cy.visit("/app/doctype/ToDo");
|
||||
cy.get(".assignments > .avatar-group > .avatar > .avatar-frame").click();
|
||||
cy.get(".remove-btn").click({ force: true });
|
||||
cy.hide_dialog();
|
||||
cy.visit("/app/doctype");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
cy.get(".empty-state").should("contain", "No filters found");
|
||||
//To remove the assignment
|
||||
cy.visit(`/app/todo/${todo_name}`);
|
||||
cy.get(".assignments > .avatar-group > .avatar > .avatar-frame").click();
|
||||
cy.get(".remove-btn").click({ force: true });
|
||||
cy.hide_dialog();
|
||||
cy.visit("/app/todo");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
cy.get(".empty-state").should("contain", "No filters found");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-console */
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const glob = require("fast-glob");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-console */
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const glob = require("fast-glob");
|
||||
|
|
|
|||
|
|
@ -94,16 +94,16 @@ function get_cli_arg(name) {
|
|||
|
||||
function log_error(message, badge = "ERROR") {
|
||||
badge = chalk.white.bgRed(` ${badge} `);
|
||||
console.error(`${badge} ${message}`); // eslint-disable-line no-console
|
||||
console.error(`${badge} ${message}`);
|
||||
}
|
||||
|
||||
function log_warn(message, badge = "WARN") {
|
||||
badge = chalk.black.bgYellowBright(` ${badge} `);
|
||||
console.warn(`${badge} ${message}`); // eslint-disable-line no-console
|
||||
console.warn(`${badge} ${message}`);
|
||||
}
|
||||
|
||||
function log(...args) {
|
||||
console.log(...args); // eslint-disable-line no-console
|
||||
console.log(...args);
|
||||
}
|
||||
|
||||
function get_redis_subscriber(kind) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ import os
|
|||
import re
|
||||
import unicodedata
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, TypeAlias, overload
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, TypeAlias, overload
|
||||
|
||||
import click
|
||||
from werkzeug.local import Local, release_local
|
||||
|
|
@ -243,7 +244,7 @@ def init(site: str, sites_path: str = ".", new_site: bool = False, force=False)
|
|||
local.preload_assets = {"style": [], "script": []}
|
||||
local.session = _dict()
|
||||
local.dev_server = _dev_server
|
||||
local.qb = get_query_builder(local.conf.db_type or "mariadb")
|
||||
local.qb = get_query_builder(local.conf.db_type)
|
||||
local.qb.get_query = get_query
|
||||
setup_redis_cache_connection()
|
||||
setup_module_map()
|
||||
|
|
@ -274,9 +275,12 @@ def connect(
|
|||
set_user("Administrator")
|
||||
|
||||
|
||||
def connect_replica():
|
||||
def connect_replica() -> bool:
|
||||
from frappe.database import get_db
|
||||
|
||||
if local and hasattr(local, "replica_db") and hasattr(local, "primary_db"):
|
||||
return False
|
||||
|
||||
user = local.conf.db_name
|
||||
password = local.conf.db_password
|
||||
port = local.conf.replica_db_port
|
||||
|
|
@ -291,6 +295,8 @@ def connect_replica():
|
|||
local.primary_db = local.db
|
||||
local.db = local.replica_db
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_site_config(sites_path: str | None = None, site_path: str | None = None) -> dict[str, Any]:
|
||||
"""Returns `site_config.json` combined with `sites/common_site_config.json`.
|
||||
|
|
@ -320,6 +326,27 @@ def get_site_config(sites_path: str | None = None, site_path: str | None = None)
|
|||
elif local.site and not local.flags.new_site:
|
||||
raise IncorrectSitePath(f"{local.site} does not exist")
|
||||
|
||||
# Generalized env variable overrides and defaults
|
||||
def db_default_ports(db_type):
|
||||
from frappe.database.mariadb.database import MariaDBDatabase
|
||||
|
||||
return {
|
||||
"mariadb": MariaDBDatabase.default_port, # 3306
|
||||
"postgres": 5432,
|
||||
}[db_type]
|
||||
|
||||
config["redis_queue"] = (
|
||||
os.environ.get("FRAPPE_REDIS_QUEUE") or config.get("redis_queue") or "redis://127.0.0.1:11311"
|
||||
)
|
||||
config["redis_cache"] = (
|
||||
os.environ.get("FRAPPE_REDIS_CACHE") or config.get("redis_cache") or "redis://127.0.0.1:13311"
|
||||
)
|
||||
config["db_type"] = os.environ.get("FRAPPE_DB_TYPE") or config.get("db_type") or "mariadb"
|
||||
config["db_host"] = os.environ.get("FRAPPE_DB_HOST") or config.get("db_host") or "127.0.0.1"
|
||||
config["db_port"] = (
|
||||
os.environ.get("FRAPPE_DB_PORT") or config.get("db_port") or db_default_ports(config["db_type"])
|
||||
)
|
||||
|
||||
return _dict(config)
|
||||
|
||||
|
||||
|
|
@ -360,7 +387,7 @@ def setup_redis_cache_connection():
|
|||
if not cache:
|
||||
from frappe.utils.redis_wrapper import RedisWrapper
|
||||
|
||||
cache = RedisWrapper.from_url(conf.get("redis_cache") or "redis://localhost:11311")
|
||||
cache = RedisWrapper.from_url(conf.get("redis_cache"))
|
||||
|
||||
|
||||
def get_traceback(with_context: bool = False) -> str:
|
||||
|
|
@ -788,13 +815,17 @@ def is_whitelisted(method):
|
|||
def read_only():
|
||||
def innfn(fn):
|
||||
def wrapper_fn(*args, **kwargs):
|
||||
|
||||
# frappe.read_only could be called from nested functions, in such cases don't swap the
|
||||
# connection again.
|
||||
switched_connection = False
|
||||
if conf.read_from_replica:
|
||||
connect_replica()
|
||||
switched_connection = connect_replica()
|
||||
|
||||
try:
|
||||
retval = fn(*args, **get_newargs(fn, kwargs))
|
||||
finally:
|
||||
if local and hasattr(local, "primary_db"):
|
||||
if switched_connection and local and hasattr(local, "primary_db"):
|
||||
local.db.close()
|
||||
local.db = local.primary_db
|
||||
|
||||
|
|
|
|||
|
|
@ -284,6 +284,8 @@ def handle_exception(e):
|
|||
or (frappe.local.request.path.startswith("/api/") and not accept_header.startswith("text"))
|
||||
)
|
||||
|
||||
allow_traceback = frappe.get_system_settings("allow_error_traceback") if frappe.db else False
|
||||
|
||||
if not frappe.session.user:
|
||||
# If session creation fails then user won't be unset. This causes a lot of code that
|
||||
# assumes presence of this to fail. Session creation fails => guest or expired login
|
||||
|
|
@ -338,7 +340,7 @@ def handle_exception(e):
|
|||
else:
|
||||
traceback = "<pre>" + sanitize_html(frappe.get_traceback()) + "</pre>"
|
||||
# disable traceback in production if flag is set
|
||||
if frappe.local.flags.disable_traceback and not frappe.local.dev_server:
|
||||
if frappe.local.flags.disable_traceback or not allow_traceback and not frappe.local.dev_server:
|
||||
traceback = ""
|
||||
|
||||
frappe.respond_as_web_page(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ from frappe.contacts.doctype.contact.contact import (
|
|||
get_contacts_linked_from,
|
||||
get_contacts_linking_to,
|
||||
)
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
|
|
@ -372,14 +374,14 @@ class AutoRepeat(Document):
|
|||
elif "{" in self.message:
|
||||
message = frappe.render_template(self.message, {"doc": new_doc})
|
||||
|
||||
frappe.sendmail(
|
||||
reference_doctype=new_doc.doctype,
|
||||
reference_name=new_doc.name,
|
||||
make(
|
||||
doctype=new_doc.doctype,
|
||||
name=new_doc.name,
|
||||
recipients=self.recipients,
|
||||
subject=subject,
|
||||
content=message,
|
||||
attachments=attachments,
|
||||
expose_recipients="header",
|
||||
send_email=1,
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ def database(context, extra_args):
|
|||
if not site:
|
||||
raise SiteNotSpecifiedError
|
||||
frappe.init(site=site)
|
||||
if not frappe.conf.db_type or frappe.conf.db_type == "mariadb":
|
||||
if frappe.conf.db_type == "mariadb":
|
||||
_mariadb(extra_args=extra_args)
|
||||
elif frappe.conf.db_type == "postgres":
|
||||
_psql(extra_args=extra_args)
|
||||
|
|
@ -505,19 +505,17 @@ def postgres(context, extra_args):
|
|||
|
||||
|
||||
def _mariadb(extra_args=None):
|
||||
from frappe.database.mariadb.database import MariaDBDatabase
|
||||
|
||||
mysql = which("mysql")
|
||||
command = [
|
||||
mysql,
|
||||
"--port",
|
||||
str(frappe.conf.db_port or MariaDBDatabase.default_port),
|
||||
str(frappe.conf.db_port),
|
||||
"-u",
|
||||
frappe.conf.db_name,
|
||||
f"-p{frappe.conf.db_password}",
|
||||
frappe.conf.db_name,
|
||||
"-h",
|
||||
frappe.conf.db_host or "localhost",
|
||||
frappe.conf.db_host,
|
||||
"--pager=less -SFX",
|
||||
"--safe-updates",
|
||||
"-A",
|
||||
|
|
@ -530,8 +528,8 @@ def _mariadb(extra_args=None):
|
|||
def _psql(extra_args=None):
|
||||
psql = which("psql")
|
||||
|
||||
host = frappe.conf.db_host or "127.0.0.1"
|
||||
port = frappe.conf.db_port or "5432"
|
||||
host = frappe.conf.db_host
|
||||
port = frappe.conf.db_port
|
||||
env = os.environ.copy()
|
||||
env["PGPASSWORD"] = frappe.conf.db_password
|
||||
conn_string = f"postgresql://{frappe.conf.db_name}@{host}:{port}/{frappe.conf.db_name}"
|
||||
|
|
@ -666,7 +664,7 @@ def transform_database(context, table, engine, row_format, failfast):
|
|||
skipped = 0
|
||||
frappe.init(site=site)
|
||||
|
||||
if frappe.conf.db_type and frappe.conf.db_type != "mariadb":
|
||||
if frappe.conf.db_type != "mariadb":
|
||||
click.secho("This command only has support for MariaDB databases at this point", fg="yellow")
|
||||
sys.exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# License: MIT. See LICENSE
|
||||
import frappe
|
||||
from frappe.contacts.doctype.contact.contact import get_full_name
|
||||
from frappe.email import get_contact_list
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
test_dependencies = ["Contact", "Salutation"]
|
||||
|
|
@ -44,6 +45,17 @@ class TestContact(FrappeTestCase):
|
|||
"John Jane Doe",
|
||||
)
|
||||
|
||||
def test_get_contact_list(self):
|
||||
# First time from database
|
||||
results = get_contact_list("_Test Supplier")
|
||||
self.assertEqual(results[0].label, "test_contact@example.com")
|
||||
self.assertEqual(results[0].description, "_Test Contact For _Test Supplier")
|
||||
|
||||
# Second time from cache
|
||||
results = get_contact_list("_Test Supplier")
|
||||
self.assertEqual(results[0].label, "test_contact@example.com")
|
||||
self.assertEqual(results[0].description, "_Test Contact For _Test Supplier")
|
||||
|
||||
|
||||
def create_contact(name, salutation, emails=None, phones=None, save=True):
|
||||
doc = frappe.get_doc(
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@ import frappe
|
|||
from frappe import _
|
||||
from frappe.core.doctype.data_import.exporter import Exporter
|
||||
from frappe.core.doctype.data_import.importer import Importer
|
||||
from frappe.model import core_doctypes_list
|
||||
from frappe.model.document import Document
|
||||
from frappe.modules.import_file import import_file_by_path
|
||||
from frappe.utils.background_jobs import enqueue, is_job_enqueued
|
||||
from frappe.utils.csvutils import validate_google_sheets_url
|
||||
|
||||
BLOCKED_DOCTYPES = set(core_doctypes_list) - {"User", "Role"}
|
||||
|
||||
|
||||
class DataImport(Document):
|
||||
def validate(self):
|
||||
|
|
@ -24,10 +27,15 @@ class DataImport(Document):
|
|||
self.template_options = ""
|
||||
self.template_warnings = ""
|
||||
|
||||
self.validate_doctype()
|
||||
self.validate_import_file()
|
||||
self.validate_google_sheets_url()
|
||||
self.set_payload_count()
|
||||
|
||||
def validate_doctype(self):
|
||||
if self.reference_doctype in BLOCKED_DOCTYPES:
|
||||
frappe.throw(_("Importing {0} is not allowed.").format(self.reference_doctype))
|
||||
|
||||
def validate_import_file(self):
|
||||
if self.import_file:
|
||||
# validate template
|
||||
|
|
|
|||
|
|
@ -2,6 +2,26 @@
|
|||
// MIT License. See license.txt
|
||||
|
||||
frappe.ui.form.on("DocType", {
|
||||
before_save: function (frm) {
|
||||
let form_builder = frappe.form_builder;
|
||||
if (form_builder?.store) {
|
||||
let fields = form_builder.store.update_fields();
|
||||
|
||||
// if fields is a string, it means there is an error
|
||||
if (typeof fields === "string") {
|
||||
frappe.throw(fields);
|
||||
}
|
||||
}
|
||||
},
|
||||
after_save: function (frm) {
|
||||
if (
|
||||
frappe.form_builder &&
|
||||
frappe.form_builder.doctype === frm.doc.name &&
|
||||
frappe.form_builder.store
|
||||
) {
|
||||
frappe.form_builder.store.fetch();
|
||||
}
|
||||
},
|
||||
refresh: function (frm) {
|
||||
frm.set_query("role", "permissions", function (doc) {
|
||||
if (doc.custom && frappe.session.user != "Administrator") {
|
||||
|
|
@ -21,8 +41,6 @@ frappe.ui.form.on("DocType", {
|
|||
frm.toggle_enable("beta", 0);
|
||||
}
|
||||
|
||||
render_form_builder_message(frm);
|
||||
|
||||
if (!frm.is_new() && !frm.doc.istable) {
|
||||
if (frm.doc.issingle) {
|
||||
frm.add_custom_button(__("Go to {0}", [__(frm.doc.name)]), () => {
|
||||
|
|
@ -72,6 +90,8 @@ frappe.ui.form.on("DocType", {
|
|||
frm.cscript.autoname(frm);
|
||||
frm.cscript.set_naming_rule_description(frm);
|
||||
frm.trigger("setup_default_views");
|
||||
|
||||
render_form_builder(frm);
|
||||
},
|
||||
|
||||
istable: (frm) => {
|
||||
|
|
@ -142,4 +162,30 @@ function render_form_builder_message(frm) {
|
|||
}
|
||||
}
|
||||
|
||||
function render_form_builder(frm) {
|
||||
if (frappe.form_builder && frappe.form_builder.doctype === frm.doc.name) {
|
||||
frappe.form_builder.setup_page_actions();
|
||||
frappe.form_builder.store.fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
if (frappe.form_builder) {
|
||||
frappe.form_builder.wrapper = $(frm.fields_dict["form_builder"].wrapper);
|
||||
frappe.form_builder.frm = frm;
|
||||
frappe.form_builder.doctype = frm.doc.name;
|
||||
frappe.form_builder.customize = false;
|
||||
frappe.form_builder.init(true);
|
||||
frappe.form_builder.store.fetch();
|
||||
} else {
|
||||
frappe.require("form_builder.bundle.js").then(() => {
|
||||
frappe.form_builder = new frappe.ui.FormBuilder({
|
||||
wrapper: $(frm.fields_dict["form_builder"].wrapper),
|
||||
frm: frm,
|
||||
doctype: frm.doc.name,
|
||||
customize: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm }));
|
||||
|
|
|
|||
|
|
@ -25,9 +25,6 @@
|
|||
"beta",
|
||||
"is_virtual",
|
||||
"queue_in_background",
|
||||
"fields_section_break",
|
||||
"try_form_builder_html",
|
||||
"fields",
|
||||
"sb1",
|
||||
"naming_rule",
|
||||
"autoname",
|
||||
|
|
@ -35,6 +32,32 @@
|
|||
"column_break_15",
|
||||
"description",
|
||||
"documentation",
|
||||
"sb2",
|
||||
"permissions",
|
||||
"restrict_to_domain",
|
||||
"read_only",
|
||||
"in_create",
|
||||
"actions_section",
|
||||
"actions",
|
||||
"links_section",
|
||||
"links",
|
||||
"document_states_section",
|
||||
"states",
|
||||
"web_view",
|
||||
"has_web_view",
|
||||
"allow_guest_to_view",
|
||||
"index_web_pages_for_search",
|
||||
"route",
|
||||
"is_published_field",
|
||||
"website_search_field",
|
||||
"advanced",
|
||||
"engine",
|
||||
"migration_hash",
|
||||
"form_builder_tab",
|
||||
"form_builder",
|
||||
"fields_section",
|
||||
"fields",
|
||||
"settings_tab",
|
||||
"form_settings_section",
|
||||
"image_field",
|
||||
"timeline_field",
|
||||
|
|
@ -68,28 +91,7 @@
|
|||
"column_break_51",
|
||||
"email_append_to",
|
||||
"sender_field",
|
||||
"subject_field",
|
||||
"sb2",
|
||||
"permissions",
|
||||
"restrict_to_domain",
|
||||
"read_only",
|
||||
"in_create",
|
||||
"actions_section",
|
||||
"actions",
|
||||
"links_section",
|
||||
"links",
|
||||
"document_states_section",
|
||||
"states",
|
||||
"web_view",
|
||||
"has_web_view",
|
||||
"allow_guest_to_view",
|
||||
"index_web_pages_for_search",
|
||||
"route",
|
||||
"is_published_field",
|
||||
"website_search_field",
|
||||
"advanced",
|
||||
"engine",
|
||||
"migration_hash"
|
||||
"subject_field"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -195,12 +197,6 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Beta"
|
||||
},
|
||||
{
|
||||
"fieldname": "fields_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Fields",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "fields",
|
||||
"fieldtype": "Table",
|
||||
|
|
@ -633,9 +629,25 @@
|
|||
"label": "Is Calendar and Gantt"
|
||||
},
|
||||
{
|
||||
"fieldname": "try_form_builder_html",
|
||||
"fieldname": "settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_builder_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Form"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_builder",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Try Form Builder HTML"
|
||||
"label": "Form Builder"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "fields_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Fields"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bolt",
|
||||
|
|
@ -718,7 +730,7 @@
|
|||
"link_fieldname": "reference_doctype"
|
||||
}
|
||||
],
|
||||
"modified": "2023-05-15 14:07:51.526257",
|
||||
"modified": "2023-07-12 13:56:26.185637",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
@ -755,4 +767,4 @@
|
|||
"states": [],
|
||||
"track_changes": 1,
|
||||
"translated_doctype": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1496,6 +1496,7 @@ def get_fields_not_allowed_in_list_view(meta) -> list[str]:
|
|||
not_allowed_in_list_view.append("Attach Image")
|
||||
if meta.istable:
|
||||
not_allowed_in_list_view.remove("Button")
|
||||
not_allowed_in_list_view.remove("HTML")
|
||||
return not_allowed_in_list_view
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
frappe.listview_settings["DocType"] = {
|
||||
onload: function (me) {
|
||||
me.page.btn_primary.addClass("hidden");
|
||||
this.setup_select_primary_button(me);
|
||||
},
|
||||
|
||||
setup_select_primary_button: function (me) {
|
||||
let actions = [
|
||||
{
|
||||
label: __("Add DocType (Form Builder)"),
|
||||
description: __("Use the form builder to create a new DocType"),
|
||||
action: () => frappe.set_route("form-builder", "new-doctype"),
|
||||
},
|
||||
{
|
||||
label: __("Add DocType"),
|
||||
description: __("Create a new DocType"),
|
||||
action: () => frappe.new_doc("DocType"),
|
||||
},
|
||||
];
|
||||
|
||||
frappe.utils.add_select_group_button(
|
||||
me.page.btn_primary.parent(),
|
||||
actions,
|
||||
"btn-primary",
|
||||
"add"
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
@ -308,7 +308,7 @@ def attach_files_to_document(doc: "Document", event) -> None:
|
|||
# we dont want the update to fail if file cannot be attached for some reason
|
||||
value = doc.get(df.fieldname)
|
||||
if not (value or "").startswith(("/files", "/private/files")):
|
||||
return
|
||||
continue
|
||||
|
||||
if frappe.db.exists(
|
||||
"File",
|
||||
|
|
@ -319,7 +319,7 @@ def attach_files_to_document(doc: "Document", event) -> None:
|
|||
"attached_to_field": df.fieldname,
|
||||
},
|
||||
):
|
||||
return
|
||||
continue
|
||||
|
||||
unattached_file = frappe.db.exists(
|
||||
"File",
|
||||
|
|
@ -341,7 +341,7 @@ def attach_files_to_document(doc: "Document", event) -> None:
|
|||
"attached_to_field": df.fieldname,
|
||||
},
|
||||
)
|
||||
return
|
||||
continue
|
||||
|
||||
file: "File" = frappe.get_doc(
|
||||
doctype="File",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// Copyright (c) {year}, {app_publisher} and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["{name}"] = {{
|
||||
"filters": [
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ frappe.ui.form.on("User", {
|
|||
|
||||
if (frappe.route_flags.unsaved === 1) {
|
||||
delete frappe.route_flags.unsaved;
|
||||
for (var i = 0; i < frm.doc.user_emails.length; i++) {
|
||||
for (let i = 0; i < frm.doc.user_emails.length; i++) {
|
||||
frm.doc.user_emails[i].idx = frm.doc.user_emails[i].idx + 1;
|
||||
}
|
||||
frm.dirty();
|
||||
|
|
@ -308,7 +308,7 @@ frappe.ui.form.on("User", {
|
|||
enable_incoming: 1,
|
||||
};
|
||||
frappe.model.with_doctype("Email Account", function (doc) {
|
||||
var doc = frappe.model.get_new_doc("Email Account");
|
||||
doc = frappe.model.get_new_doc("Email Account");
|
||||
frappe.route_flags.linked_user = frm.doc.name;
|
||||
frappe.route_flags.delete_user_from_locals = true;
|
||||
frappe.set_route("Form", "Email Account", doc.name);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
from collections.abc import Sequence
|
||||
from datetime import timedelta
|
||||
from typing import Optional, Sequence
|
||||
from typing import Optional
|
||||
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ class UserPermission(Document):
|
|||
frappe.throw(_("{0} has already assigned default value for {1}.").format(ref_link, self.allow))
|
||||
|
||||
|
||||
def send_user_permissions(bootinfo):
|
||||
bootinfo.user["user_permissions"] = get_user_permissions()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_user_permissions(user=None):
|
||||
"""Get all users permissions for the user as a dict of doctype"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// Copyright (c) 2022, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Database Storage Usage By Tables"] = {
|
||||
filters: [],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Transaction Log Report"] = {
|
||||
onload: function (query_report) {
|
||||
|
|
|
|||
|
|
@ -100,8 +100,6 @@ frappe.ui.form.on("Customize Form", {
|
|||
frm.page.set_title(__("Customize Form - {0}", [frm.doc.doc_type]));
|
||||
frappe.customize_form.set_primary_action(frm);
|
||||
|
||||
render_form_builder_message(frm);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Go to {0} List", [__(frm.doc.doc_type)]),
|
||||
function () {
|
||||
|
|
@ -149,6 +147,8 @@ frappe.ui.form.on("Customize Form", {
|
|||
["queue_in_background"],
|
||||
frappe.get_meta(frm.doc.doc_type).is_submittable || 0
|
||||
);
|
||||
|
||||
render_form_builder(frm);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -334,37 +334,6 @@ frappe.ui.form.on("DocType State", {
|
|||
},
|
||||
});
|
||||
|
||||
frappe.customize_form.validate_fieldnames = async function (frm) {
|
||||
for (let i = 0; i < frm.doc.fields.length; i++) {
|
||||
let field = frm.doc.fields[i];
|
||||
|
||||
let fieldname = field.label && frappe.model.scrub(field.label).toLowerCase();
|
||||
if (
|
||||
field.label &&
|
||||
!field.fieldname &&
|
||||
in_list(frappe.model.restricted_fields, fieldname)
|
||||
) {
|
||||
let message = __(
|
||||
"For field <b>{0}</b> in row <b>{1}</b>, fieldname <b>{2}</b> is restricted it will be renamed as <b>{2}1</b>. Do you want to continue?",
|
||||
[field.label, field.idx, fieldname]
|
||||
);
|
||||
await pause_to_confirm(message);
|
||||
}
|
||||
}
|
||||
|
||||
function pause_to_confirm(message) {
|
||||
return new Promise((resolve) => {
|
||||
frappe.confirm(
|
||||
message,
|
||||
() => resolve(),
|
||||
() => {
|
||||
frm.page.btn_primary.prop("disabled", false);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
frappe.customize_form.save_customization = function (frm) {
|
||||
if (frm.doc.doc_type) {
|
||||
return frm.call({
|
||||
|
|
@ -383,9 +352,22 @@ frappe.customize_form.save_customization = function (frm) {
|
|||
}
|
||||
};
|
||||
|
||||
frappe.customize_form.update_fields_from_form_builder = function (frm) {
|
||||
let form_builder = frappe.form_builder;
|
||||
if (form_builder?.store) {
|
||||
let fields = form_builder.store.update_fields();
|
||||
|
||||
// if fields is a string, it means there is an error
|
||||
if (typeof fields === "string") {
|
||||
frappe.throw(fields);
|
||||
}
|
||||
frm.refresh_fields();
|
||||
}
|
||||
};
|
||||
|
||||
frappe.customize_form.set_primary_action = function (frm) {
|
||||
frm.page.set_primary_action(__("Update"), async () => {
|
||||
await this.validate_fieldnames(frm);
|
||||
frm.page.set_primary_action(__("Update"), () => {
|
||||
this.update_fields_from_form_builder(frm);
|
||||
this.save_customization(frm);
|
||||
});
|
||||
};
|
||||
|
|
@ -433,30 +415,29 @@ frappe.customize_form.clear_locals_and_refresh = function (frm) {
|
|||
frm.refresh();
|
||||
};
|
||||
|
||||
function render_form_builder_message(frm) {
|
||||
$(frm.fields_dict["try_form_builder_html"].wrapper).empty();
|
||||
if (!frm.is_new() && frm.fields_dict["try_form_builder_html"]) {
|
||||
let title = __("Use Form Builder to visually customize your form layout");
|
||||
let msg = __(
|
||||
"You can drag and drop fields to create your form layout, add tabs, sections and columns to organize your form and update field properties all from one screen."
|
||||
);
|
||||
function render_form_builder(frm) {
|
||||
if (frappe.form_builder && frappe.form_builder.doctype === frm.doc.doc_type) {
|
||||
frappe.form_builder.setup_page_actions();
|
||||
frappe.form_builder.store.fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
let message = `
|
||||
<div class="flex form-message blue p-3">
|
||||
<div class="mr-3"><img style="border-radius: var(--border-radius-md)" width="275" src="/assets/frappe/images/form-builder.gif"></div>
|
||||
<div>
|
||||
<p style="font-size: var(--text-lg)">${title}</p>
|
||||
<p>${msg}</p>
|
||||
<div>
|
||||
<a class="btn btn-primary btn-sm" href="/app/form-builder/${frm.doc.doc_type}/customize">
|
||||
${__("Form Builder")} ${frappe.utils.icon("right", "xs")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$(frm.fields_dict["try_form_builder_html"].wrapper).html(message);
|
||||
if (frappe.form_builder) {
|
||||
frappe.form_builder.wrapper = $(frm.fields_dict["form_builder"].wrapper);
|
||||
frappe.form_builder.frm = frm;
|
||||
frappe.form_builder.doctype = frm.doc.doc_type;
|
||||
frappe.form_builder.customize = true;
|
||||
frappe.form_builder.init(true);
|
||||
frappe.form_builder.store.fetch();
|
||||
} else {
|
||||
frappe.require("form_builder.bundle.js").then(() => {
|
||||
frappe.form_builder = new frappe.ui.FormBuilder({
|
||||
wrapper: $(frm.fields_dict["form_builder"].wrapper),
|
||||
frm: frm,
|
||||
doctype: frm.doc.doc_type,
|
||||
customize: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,20 @@
|
|||
"allow_auto_repeat",
|
||||
"allow_import",
|
||||
"queue_in_background",
|
||||
"fields_section_break",
|
||||
"try_form_builder_html",
|
||||
"fields",
|
||||
"naming_section",
|
||||
"naming_rule",
|
||||
"autoname",
|
||||
"document_actions_section",
|
||||
"actions",
|
||||
"document_links_section",
|
||||
"links",
|
||||
"document_states_section",
|
||||
"states",
|
||||
"form_tab",
|
||||
"form_builder",
|
||||
"fields_section_break",
|
||||
"fields",
|
||||
"settings_tab",
|
||||
"form_settings_section",
|
||||
"image_field",
|
||||
"max_attachments",
|
||||
|
|
@ -48,12 +56,6 @@
|
|||
"email_append_to",
|
||||
"sender_field",
|
||||
"subject_field",
|
||||
"document_actions_section",
|
||||
"actions",
|
||||
"document_links_section",
|
||||
"links",
|
||||
"document_states_section",
|
||||
"states",
|
||||
"section_break_8",
|
||||
"sort_field",
|
||||
"column_break_10",
|
||||
|
|
@ -174,8 +176,8 @@
|
|||
"options": "ASC\nDESC"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "doc_type",
|
||||
"description": "Customize Label, Print Hide, Default etc.",
|
||||
"fieldname": "fields_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Fields"
|
||||
|
|
@ -369,9 +371,19 @@
|
|||
"label": "Is Calendar and Gantt"
|
||||
},
|
||||
{
|
||||
"fieldname": "try_form_builder_html",
|
||||
"fieldname": "settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_builder",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Try Form Builder HTML"
|
||||
"label": "Form Builder"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Form"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
|
|
@ -380,7 +392,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-15 16:03:19.872532",
|
||||
"modified": "2023-07-16 13:25:46.201184",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form",
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ frappe.ui.form.on("DocType Layout", {
|
|||
await frm.events.sync_fields(frm, false);
|
||||
|
||||
if (frm.is_new()) {
|
||||
frm.doc.__newname = document_name;
|
||||
frm.doc.__newname = document_name; // eslint-disable-line
|
||||
frm.refresh_field("__newname");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// Copyright (c) 2023, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Audit System Hooks"] = {
|
||||
filters: [],
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ import random
|
|||
import re
|
||||
import string
|
||||
import traceback
|
||||
from collections.abc import Iterable, Sequence
|
||||
from contextlib import contextmanager, suppress
|
||||
from time import time
|
||||
from typing import Any, Iterable, Sequence
|
||||
from typing import Any
|
||||
|
||||
from pypika.dialects import MySQLQueryBuilder, PostgreSQLQueryBuilder
|
||||
from pypika.terms import Criterion, NullValue
|
||||
|
|
@ -87,8 +88,8 @@ class Database:
|
|||
port=None,
|
||||
):
|
||||
self.setup_type_map()
|
||||
self.host = host or frappe.conf.db_host or "127.0.0.1"
|
||||
self.port = port or frappe.conf.db_port or ""
|
||||
self.host = host or frappe.conf.db_host
|
||||
self.port = port or frappe.conf.db_port
|
||||
self.user = user or frappe.conf.db_name
|
||||
self.db_name = frappe.conf.db_name
|
||||
self._conn = None
|
||||
|
|
|
|||
|
|
@ -68,11 +68,7 @@ class DbManager:
|
|||
if pipe:
|
||||
print("Restoring Database file...")
|
||||
|
||||
command = (
|
||||
"{pipe} mysql -u {user} -p{password} -h{host} "
|
||||
+ ("-P{port}" if frappe.db.port else "")
|
||||
+ " {target} {source}"
|
||||
)
|
||||
command = "{pipe} mysql -u {user} -p{password} -h{host} -P{port} {target} {source}"
|
||||
command = command.format(
|
||||
pipe=pipe,
|
||||
user=esc(user),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# MIT License. See license.txt
|
||||
|
||||
import operator
|
||||
from typing import Callable
|
||||
from collections.abc import Callable
|
||||
|
||||
import frappe
|
||||
from frappe.database.utils import NestedSetHierarchy
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ def import_db_from_sql(source_sql=None, verbose=False):
|
|||
|
||||
_command = (
|
||||
f"psql {frappe.conf.db_name} "
|
||||
f"-h {frappe.conf.db_host or 'localhost'} -p {str(frappe.conf.db_port or '5432')} "
|
||||
f"-h {frappe.conf.db_host} -p {str(frappe.conf.db_port)} "
|
||||
f"-U {frappe.conf.db_name}"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@
|
|||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"script",
|
||||
"output"
|
||||
"script"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -16,20 +15,15 @@
|
|||
"in_list_view": 1,
|
||||
"label": "Script",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "output",
|
||||
"fieldtype": "Code",
|
||||
"label": "Output",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-18 20:07:57.587344",
|
||||
"modified": "2023-07-05 22:16:02.823955",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Console Log",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
|
@ -48,5 +42,6 @@
|
|||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
"label": "Event Type",
|
||||
"oldfieldname": "event_type",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Private\nPublic\nCancelled",
|
||||
"options": "Private\nPublic",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
|
|
@ -223,7 +223,7 @@
|
|||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"options": "Open\nCompleted\nClosed"
|
||||
"options": "Open\nCompleted\nClosed\nCancelled"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
|
|
@ -295,7 +295,7 @@
|
|||
"icon": "fa fa-calendar",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2022-08-12 19:24:34.794098",
|
||||
"modified": "2023-06-23 10:33:15.685368",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Event",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ frappe.ui.form.on("Form Tour", {
|
|||
frm.set_query("reference_doctype", () => {
|
||||
return { filters: { istable: 0 } };
|
||||
});
|
||||
frm.trigger("reference_doctype");
|
||||
frm.set_query("report_name", () => {
|
||||
if (frm.doc.reference_doctype) {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class SystemConsole(Document):
|
|||
else:
|
||||
frappe.db.rollback()
|
||||
|
||||
frappe.get_doc(dict(doctype="Console Log", script=self.console, output=self.output)).insert()
|
||||
frappe.get_doc(dict(doctype="Console Log", script=self.console)).insert()
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,211 +0,0 @@
|
|||
frappe.pages["form-builder"].on_page_load = function (wrapper) {
|
||||
frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: __("Form Builder"),
|
||||
single_column: true,
|
||||
});
|
||||
|
||||
// hot reload in development
|
||||
if (frappe.boot.developer_mode) {
|
||||
frappe.hot_update = frappe.hot_update || [];
|
||||
frappe.hot_update.push(() => load_form_builder(wrapper));
|
||||
}
|
||||
};
|
||||
|
||||
frappe.pages["form-builder"].on_page_show = function (wrapper) {
|
||||
load_form_builder(wrapper);
|
||||
};
|
||||
|
||||
function load_form_builder(wrapper) {
|
||||
let route = frappe.get_route();
|
||||
route = route.filter((a) => a);
|
||||
|
||||
if (route.length > 1 && route[1] === "new-doctype") {
|
||||
frappe.pages["form-builder"].new_doctype(route[2]);
|
||||
} else if (route.length > 1) {
|
||||
let doctype = route[1];
|
||||
let is_customize_form = route[2] === "customize";
|
||||
|
||||
if (frappe.form_builder?.doctype) {
|
||||
frappe.form_builder.doctype = frappe.form_builder.store.doctype = doctype;
|
||||
frappe.form_builder.customize = frappe.form_builder.store.is_customize_form =
|
||||
is_customize_form;
|
||||
frappe.form_builder.init(true);
|
||||
frappe.form_builder.store.fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
let $parent = $(wrapper).find(".layout-main-section");
|
||||
$parent.empty();
|
||||
|
||||
frappe.require("form_builder.bundle.js").then(() => {
|
||||
frappe.form_builder = new frappe.ui.FormBuilder({
|
||||
wrapper: $parent,
|
||||
page: wrapper.page,
|
||||
doctype: doctype,
|
||||
customize: is_customize_form,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
frappe.pages["form-builder"].select_doctype();
|
||||
}
|
||||
}
|
||||
|
||||
frappe.pages["form-builder"].select_doctype = function () {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Select DocType"),
|
||||
fields: [
|
||||
{
|
||||
label: __("Select DocType"),
|
||||
fieldname: "doctype",
|
||||
fieldtype: "Link",
|
||||
options: "DocType",
|
||||
only_select: 1,
|
||||
},
|
||||
{
|
||||
label: __("Customize"),
|
||||
fieldname: "customize",
|
||||
fieldtype: "Check",
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Edit"),
|
||||
primary_action({ doctype, customize }) {
|
||||
if (customize) {
|
||||
frappe.model.with_doctype(doctype).then(() => {
|
||||
let meta = frappe.get_meta(doctype);
|
||||
if (in_list(frappe.model.core_doctypes_list, this.doctype))
|
||||
frappe.throw(__("Core DocTypes cannot be customized."));
|
||||
|
||||
if (meta.issingle) frappe.throw(__("Single DocTypes cannot be customized."));
|
||||
|
||||
if (meta.custom)
|
||||
frappe.throw(
|
||||
__(
|
||||
"Only standard DocTypes are allowed to be customized from Customize Form."
|
||||
)
|
||||
);
|
||||
frappe.set_route("form-builder", doctype, "customize");
|
||||
});
|
||||
} else {
|
||||
frappe.set_route("form-builder", doctype);
|
||||
}
|
||||
},
|
||||
secondary_action_label: __("Create New DocType"),
|
||||
secondary_action() {
|
||||
let doctype = d.get_value("doctype") || "";
|
||||
d.hide();
|
||||
frappe.set_route("form-builder", "new-doctype", doctype);
|
||||
},
|
||||
});
|
||||
|
||||
d.show();
|
||||
};
|
||||
|
||||
frappe.pages["form-builder"].new_doctype = function (doctype) {
|
||||
let non_developer = frappe.session.user !== "Administrator" || !frappe.boot.developer_mode;
|
||||
let new_d = new frappe.ui.Dialog({
|
||||
title: __("Create New DocType"),
|
||||
fields: [
|
||||
{
|
||||
label: __("DocType Name"),
|
||||
fieldname: "doctype_name",
|
||||
fieldtype: "Data",
|
||||
default: doctype,
|
||||
reqd: 1,
|
||||
},
|
||||
{ fieldtype: "Column Break" },
|
||||
{
|
||||
label: __("Module"),
|
||||
fieldname: "module",
|
||||
fieldtype: "Link",
|
||||
options: "Module Def",
|
||||
reqd: 1,
|
||||
},
|
||||
{ fieldtype: "Section Break" },
|
||||
{
|
||||
label: __("Is Submittable"),
|
||||
fieldname: "is_submittable",
|
||||
fieldtype: "Check",
|
||||
description: __(
|
||||
"Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended."
|
||||
),
|
||||
depends_on: "eval:!doc.istable && !doc.issingle",
|
||||
},
|
||||
{
|
||||
label: __("Is Child Table"),
|
||||
fieldname: "istable",
|
||||
fieldtype: "Check",
|
||||
description: __("Child Tables are shown as a Grid in other DocTypes"),
|
||||
depends_on: "eval:!doc.is_submittable && !doc.issingle",
|
||||
},
|
||||
{
|
||||
label: __("Editable Grid"),
|
||||
fieldname: "editable_grid",
|
||||
fieldtype: "Check",
|
||||
depends_on: "istable",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
label: __("Is Single"),
|
||||
fieldname: "issingle",
|
||||
fieldtype: "Check",
|
||||
description: __(
|
||||
"Single Types have only one record no tables associated. Values are stored in tabSingles"
|
||||
),
|
||||
depends_on: "eval:!doc.istable && !doc.is_submittable",
|
||||
},
|
||||
{
|
||||
label: __("Custom?"),
|
||||
fieldname: "custom",
|
||||
fieldtype: "Check",
|
||||
default: non_developer,
|
||||
read_only: non_developer,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Create & Continue"),
|
||||
primary_action(values) {
|
||||
if (!values.istable) values.editable_grid = 0;
|
||||
frappe.db
|
||||
.insert({
|
||||
doctype: "DocType",
|
||||
name: values.doctype_name,
|
||||
module: values.module,
|
||||
istable: values.istable,
|
||||
editable_grid: values.editable_grid,
|
||||
issingle: values.issingle,
|
||||
custom: values.custom,
|
||||
is_submittable: values.is_submittable,
|
||||
permissions: [
|
||||
{
|
||||
create: 1,
|
||||
delete: 1,
|
||||
email: 1,
|
||||
export: 1,
|
||||
print: 1,
|
||||
read: 1,
|
||||
report: 1,
|
||||
role: "System Manager",
|
||||
share: 1,
|
||||
write: 1,
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
label: "Title",
|
||||
fieldname: "title",
|
||||
fieldtype: "Data",
|
||||
},
|
||||
],
|
||||
})
|
||||
.then((doc) => {
|
||||
frappe.set_route("form-builder", doc.name);
|
||||
});
|
||||
},
|
||||
secondary_action_label: __("Back"),
|
||||
secondary_action() {
|
||||
new_d.hide();
|
||||
window.history.back();
|
||||
},
|
||||
});
|
||||
new_d.show();
|
||||
};
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"content": null,
|
||||
"creation": "2022-10-10 22:42:53.597423",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2022-10-10 22:42:53.597423",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "form-builder",
|
||||
"owner": "Administrator",
|
||||
"page_name": "form-builder",
|
||||
"roles": [],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Form Builder"
|
||||
}
|
||||
|
|
@ -642,7 +642,7 @@ function guess_country(country_info) {
|
|||
try {
|
||||
const system_timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
for ([country, info] of Object.entries(country_info)) {
|
||||
for (let [country, info] of Object.entries(country_info)) {
|
||||
let possible_timezones = (info.timezones || []).filter((t) => t == system_timezone);
|
||||
if (possible_timezones.length) return country;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ def email_setup_wizard_exception(traceback, args):
|
|||
traceback=traceback,
|
||||
args="\n".join(pretty_args),
|
||||
user=frappe.session.user,
|
||||
headers=frappe.request.headers,
|
||||
headers=frappe.request.headers if frappe.request else "[no request]",
|
||||
)
|
||||
|
||||
frappe.sendmail(
|
||||
|
|
|
|||
|
|
@ -145,7 +145,6 @@ class UserProfile {
|
|||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
render_percentage_chart(field, title) {
|
||||
frappe
|
||||
.xcall(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// Copyright (c) 2016, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["ToDo"] = {
|
||||
filters: [],
|
||||
|
|
|
|||
|
|
@ -525,47 +525,47 @@ def get_stats(stats, doctype, filters=None):
|
|||
|
||||
if filters is None:
|
||||
filters = []
|
||||
tags = json.loads(stats)
|
||||
columns = json.loads(stats)
|
||||
if filters:
|
||||
filters = json.loads(filters)
|
||||
stats = {}
|
||||
results = {}
|
||||
|
||||
try:
|
||||
columns = frappe.db.get_table_columns(doctype)
|
||||
db_columns = frappe.db.get_table_columns(doctype)
|
||||
except (frappe.db.InternalError, frappe.db.ProgrammingError):
|
||||
# raised when _user_tags column is added on the fly
|
||||
# raised if its a virtual doctype
|
||||
columns = []
|
||||
db_columns = []
|
||||
|
||||
for tag in tags:
|
||||
if tag not in columns:
|
||||
for column in columns:
|
||||
if column not in db_columns:
|
||||
continue
|
||||
try:
|
||||
tag_count = frappe.get_list(
|
||||
doctype,
|
||||
fields=[tag, "count(*)"],
|
||||
filters=filters + [[tag, "!=", ""]],
|
||||
group_by=tag,
|
||||
fields=[column, "count(*)"],
|
||||
filters=filters + [[column, "!=", ""]],
|
||||
group_by=column,
|
||||
as_list=True,
|
||||
distinct=1,
|
||||
)
|
||||
|
||||
if tag == "_user_tags":
|
||||
stats[tag] = scrub_user_tags(tag_count)
|
||||
if column == "_user_tags":
|
||||
results[column] = scrub_user_tags(tag_count)
|
||||
no_tag_count = frappe.get_list(
|
||||
doctype,
|
||||
fields=[tag, "count(*)"],
|
||||
filters=filters + [[tag, "in", ("", ",")]],
|
||||
fields=[column, "count(*)"],
|
||||
filters=filters + [[column, "in", ("", ",")]],
|
||||
as_list=True,
|
||||
group_by=tag,
|
||||
order_by=tag,
|
||||
group_by=column,
|
||||
order_by=column,
|
||||
)
|
||||
|
||||
no_tag_count = no_tag_count[0][1] if no_tag_count else 0
|
||||
|
||||
stats[tag].append([_("No Tags"), no_tag_count])
|
||||
results[column].append([_("No Tags"), no_tag_count])
|
||||
else:
|
||||
stats[tag] = tag_count
|
||||
results[column] = tag_count
|
||||
|
||||
except frappe.db.SQLError:
|
||||
pass
|
||||
|
|
@ -573,7 +573,7 @@ def get_stats(stats, doctype, filters=None):
|
|||
# raised when _user_tags column is added on the fly
|
||||
pass
|
||||
|
||||
return stats
|
||||
return results
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ def get_csv_bytes(data: list[list], csv_params: dict) -> bytes:
|
|||
|
||||
def provide_binary_file(filename: str, extension: str, content: bytes) -> None:
|
||||
"""Provide a binary file to the client."""
|
||||
from frappe import _
|
||||
|
||||
frappe.response["type"] = "binary"
|
||||
frappe.response["filecontent"] = content
|
||||
frappe.response["filename"] = f"{filename}.{extension}"
|
||||
frappe.response["filename"] = f"{_(filename)}.{extension}"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
# License: MIT. See LICENSE
|
||||
|
||||
import frappe
|
||||
from frappe.desk.reportview import build_match_conditions
|
||||
|
||||
|
||||
def sendmail_to_system_managers(subject, content):
|
||||
|
|
@ -12,31 +11,38 @@ def sendmail_to_system_managers(subject, content):
|
|||
@frappe.whitelist()
|
||||
def get_contact_list(txt, page_length=20) -> list[dict]:
|
||||
"""Return email ids for a multiselect field."""
|
||||
from frappe.contacts.doctype.contact.contact import get_full_name
|
||||
|
||||
if cached_contacts := get_cached_contacts(txt):
|
||||
return cached_contacts[:page_length]
|
||||
|
||||
reportview_conditions = build_match_conditions("Contact")
|
||||
match_conditions = f"and {reportview_conditions}" if reportview_conditions else ""
|
||||
fields = ["name", "first_name", "middle_name", "last_name", "company_name"]
|
||||
contacts = frappe.get_list(
|
||||
"Contact",
|
||||
fields=fields + ["`tabContact Email`.email_id"],
|
||||
filters=[
|
||||
["Contact Email", "email_id", "is", "set"],
|
||||
],
|
||||
or_filters=[[field, "like", f"%{txt}%"] for field in fields]
|
||||
+ [["Contact Email", "email_id", "like", f"%{txt}%"]],
|
||||
limit_page_length=page_length,
|
||||
)
|
||||
|
||||
# The multiselect field will store the `label` as the selected value.
|
||||
# The `value` is just used as a unique key to distinguish between the options.
|
||||
# https://github.com/frappe/frappe/blob/6c6a89bcdd9454060a1333e23b855d0505c9ebc2/frappe/public/js/frappe/form/controls/autocomplete.js#L29-L35
|
||||
out = frappe.db.sql(
|
||||
f"""select name as value, email_id as label,
|
||||
concat(first_name, ifnull(concat(' ',last_name), '' )) as description
|
||||
from tabContact
|
||||
where (name like %(txt)s or email_id like %(txt)s) and email_id != ''
|
||||
{match_conditions}
|
||||
limit %(page_length)s""",
|
||||
{"txt": f"%{txt}%", "page_length": page_length},
|
||||
as_dict=True,
|
||||
)
|
||||
out = list(filter(None, out))
|
||||
result = [
|
||||
frappe._dict(
|
||||
value=d.name,
|
||||
label=d.email_id,
|
||||
description=get_full_name(d.first_name, d.middle_name, d.last_name, d.company_name),
|
||||
)
|
||||
for d in contacts
|
||||
]
|
||||
|
||||
update_contact_cache(out)
|
||||
update_contact_cache(result)
|
||||
|
||||
return out
|
||||
return result
|
||||
|
||||
|
||||
def get_system_managers():
|
||||
|
|
|
|||
|
|
@ -857,7 +857,7 @@ class InboundMail(Email):
|
|||
"""Remove Prefixes like 'fw', FWD', 're' etc from subject."""
|
||||
# Match strings like "fw:", "re :" etc.
|
||||
regex = r"(^\s*(fw|fwd|wg)[^:]*:|\s*(re|aw)[^:]*:\s*)*"
|
||||
return frappe.as_unicode(strip(re.sub(regex, "", subject, 0, flags=re.IGNORECASE)))
|
||||
return frappe.as_unicode(strip(re.sub(regex, "", subject, count=0, flags=re.IGNORECASE)))
|
||||
|
||||
@staticmethod
|
||||
def get_email_fields(doctype):
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ class SiteNotSpecifiedError(Exception):
|
|||
super(Exception, self).__init__(self.message)
|
||||
|
||||
|
||||
class UrlSchemeNotSupported(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
http_status_code = 417
|
||||
|
||||
|
|
|
|||
|
|
@ -427,6 +427,11 @@ after_job = [
|
|||
|
||||
extend_bootinfo = [
|
||||
"frappe.utils.telemetry.add_bootinfo",
|
||||
"frappe.core.doctype.user_permission.user_permission.send_user_permissions",
|
||||
]
|
||||
|
||||
naming_series_variables = {
|
||||
"PM": "frappe.tests.test_naming.parse_naming_series_variable",
|
||||
}
|
||||
|
||||
get_changelog_feed = "frappe.desk.doctype.changelog_feed.changelog_feed.get_feed"
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ def install_db(
|
|||
from frappe.database import setup_database
|
||||
|
||||
if not db_type:
|
||||
db_type = frappe.conf.db_type or "mariadb"
|
||||
db_type = frappe.conf.db_type
|
||||
|
||||
if not root_login and db_type == "mariadb":
|
||||
root_login = "root"
|
||||
|
|
@ -772,7 +772,7 @@ def is_downgrade(sql_file_path, verbose=False):
|
|||
|
||||
# This function is only tested with mariadb
|
||||
# TODO: Add postgres support
|
||||
if frappe.conf.db_type not in (None, "mariadb"):
|
||||
if frappe.conf.db_type != "mariadb":
|
||||
return False
|
||||
|
||||
from semantic_version import Version
|
||||
|
|
@ -824,7 +824,7 @@ def is_partial(sql_file_path):
|
|||
def partial_restore(sql_file_path, verbose=False):
|
||||
sql_file = extract_sql_from_archive(sql_file_path)
|
||||
|
||||
if frappe.conf.db_type in (None, "mariadb"):
|
||||
if frappe.conf.db_type == "mariadb":
|
||||
from frappe.database.mariadb.setup_db import import_db_from_sql
|
||||
elif frappe.conf.db_type == "postgres":
|
||||
import warnings
|
||||
|
|
|
|||
|
|
@ -481,7 +481,7 @@ def update_event_in_google_calendar(doc, method=None):
|
|||
event["description"] = doc.description
|
||||
event["recurrence"] = repeat_on_to_google_calendar_recurrence_rule(doc)
|
||||
event["status"] = (
|
||||
"cancelled" if doc.event_type == "Cancelled" or doc.status == "Closed" else event.get("status")
|
||||
"cancelled" if doc.status == "Cancelled" or doc.status == "Closed" else event.get("status")
|
||||
)
|
||||
event.update(
|
||||
format_date_according_to_google_calendar(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
import hashlib
|
||||
import json
|
||||
import time
|
||||
from typing import Any, Generator, Iterable
|
||||
from collections.abc import Generator, Iterable
|
||||
from typing import Any
|
||||
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
import datetime
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Callable, Optional
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
|
@ -338,11 +339,11 @@ def parse_naming_series(
|
|||
part = determine_consecutive_week_number(today)
|
||||
elif e == "timestamp":
|
||||
part = str(today)
|
||||
elif e == "FY":
|
||||
part = frappe.defaults.get_user_default("fiscal_year")
|
||||
elif doc and (e.startswith("{") or doc.get(e, _sentinel) is not _sentinel):
|
||||
e = e.replace("{", "").replace("}", "")
|
||||
part = doc.get(e)
|
||||
elif method := has_custom_parser(e):
|
||||
part = frappe.get_attr(method[0])(doc, e)
|
||||
else:
|
||||
part = e
|
||||
|
||||
|
|
@ -354,6 +355,11 @@ def parse_naming_series(
|
|||
return name
|
||||
|
||||
|
||||
def has_custom_parser(e):
|
||||
"""Returns true if the naming series part has a custom parser"""
|
||||
return frappe.get_hooks("naming_series_variables", {}).get(e)
|
||||
|
||||
|
||||
def determine_consecutive_week_number(datetime):
|
||||
"""Determines the consecutive calendar week"""
|
||||
m = datetime.month
|
||||
|
|
|
|||
|
|
@ -226,3 +226,4 @@ frappe.desk.doctype.form_tour.patches.introduce_ui_tours
|
|||
execute:frappe.delete_doc_if_exists("Workspace", "Customization")
|
||||
execute:frappe.db.set_single_value("Document Naming Settings", "default_amend_naming", "Amend Counter")
|
||||
execute:frappe.delete_doc_if_exists("DocType", "Error Snapshot")
|
||||
frappe.patches.v15_0.move_event_cancelled_to_status
|
||||
|
|
|
|||
12
frappe/patches/v15_0/move_event_cancelled_to_status.py
Normal file
12
frappe/patches/v15_0/move_event_cancelled_to_status.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
Event = frappe.qb.DocType("Event")
|
||||
query = (
|
||||
frappe.qb.update(Event)
|
||||
.set(Event.event_type, "Private")
|
||||
.set(Event.status, "Cancelled")
|
||||
.where(Event.event_type == "Cancelled")
|
||||
)
|
||||
query.run()
|
||||
|
|
@ -93,10 +93,10 @@ def has_permission(
|
|||
doc = frappe.get_doc(meta.name, doc)
|
||||
perm = get_doc_permissions(doc, user=user, ptype=ptype).get(ptype)
|
||||
if not perm:
|
||||
push_perm_check_log(
|
||||
_("User {0} does not have access to this document").format(frappe.bold(user))
|
||||
+ f": {_(doc.doctype)} - {doc.name}"
|
||||
)
|
||||
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)
|
||||
else:
|
||||
if ptype == "submit" and not cint(meta.is_submittable):
|
||||
push_perm_check_log(_("Document Type is not submittable"))
|
||||
|
|
|
|||
|
|
@ -372,10 +372,11 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder {
|
|||
if (!$item.hasClass("print-format-builder-field")) {
|
||||
var fieldname = $item.attr("data-fieldname");
|
||||
|
||||
let field;
|
||||
if (fieldname === "_custom_html") {
|
||||
var field = me.get_custom_html_field();
|
||||
field = me.get_custom_html_field();
|
||||
} else {
|
||||
var field = frappe.meta.get_docfield(me.print_format.doc_type, fieldname);
|
||||
field = frappe.meta.get_docfield(me.print_format.doc_type, fieldname);
|
||||
}
|
||||
|
||||
var html = frappe.render_template("print_format_builder_field", {
|
||||
|
|
@ -561,7 +562,7 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder {
|
|||
resize();
|
||||
} else if (new_no_of_columns > no_of_columns) {
|
||||
// add empty column and resize old columns
|
||||
for (var i = no_of_columns; i < new_no_of_columns; i++) {
|
||||
for (let i = no_of_columns; i < new_no_of_columns; i++) {
|
||||
var col = $(
|
||||
'<div class="section-column">\
|
||||
<div class="print-format-builder-column"></div></div>'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Sidebar from "./components/Sidebar.vue"
|
|||
import Tabs from "./components/Tabs.vue";
|
||||
import { computed, onMounted, watch, ref } from "vue";
|
||||
import { useStore } from "./store";
|
||||
import { onClickOutside, useMagicKeys, whenever } from "@vueuse/core";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
|
||||
let store = useStore();
|
||||
|
||||
|
|
@ -14,19 +14,6 @@ let should_render = computed(() => {
|
|||
let container = ref(null);
|
||||
onClickOutside(container, () => store.form.selected_field = null);
|
||||
|
||||
// cmd/ctrl + s to save the form
|
||||
const { meta_s, ctrl_s } = useMagicKeys();
|
||||
whenever(() => meta_s.value || ctrl_s.value, () => {
|
||||
if (store.dirty) {
|
||||
store.save_changes();
|
||||
}
|
||||
});
|
||||
|
||||
function setup_change_doctype_dialog() {
|
||||
store.page.$title_area.on("click", () => {
|
||||
frappe.pages["form-builder"].select_doctype();
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => store.form.layout,
|
||||
|
|
@ -34,10 +21,7 @@ watch(
|
|||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
store.fetch();
|
||||
setup_change_doctype_dialog();
|
||||
});
|
||||
onMounted(() => store.fetch());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -62,9 +46,8 @@ onMounted(() => {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.form-builder-container {
|
||||
margin-bottom: -60px;
|
||||
margin: -12px -20px -5px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
||||
&.resizing {
|
||||
user-select: none;
|
||||
|
|
@ -79,12 +62,20 @@ onMounted(() => {
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.form-sidebar,
|
||||
.form-sidebar {
|
||||
border-right: 1px solid var(--border-color);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.form-main {
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--card-shadow);
|
||||
background-color: var(--card-bg);
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.form-sidebar,
|
||||
.form-main {
|
||||
:deep(.section-columns.has-one-column .field) {
|
||||
input.form-control, .signature-field {
|
||||
width: calc(50% - 19px);
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ function on_drag_end(evt) {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.fields-container {
|
||||
height: calc(100vh - 250px);
|
||||
height: calc(100vh - 233px);
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ watch(
|
|||
opacity: 0;
|
||||
background-color: var(--bg-gray);
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 10;
|
||||
z-index: 4;
|
||||
cursor: col-resize;
|
||||
|
||||
&:hover, &.resizing {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ function activate_tab(tab) {
|
|||
nextTick(() => {
|
||||
$(".tabs .tab.active")[0].scrollIntoView({
|
||||
behavior: "smooth",
|
||||
inline: "center"
|
||||
inline: "center",
|
||||
block: "nearest",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -280,6 +281,7 @@ function delete_tab(with_children) {
|
|||
.tab-contents {
|
||||
max-height: calc(100vh - 210px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border-radius: var(--border-radius);
|
||||
min-height: 70px;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ let doctype_df = computed(() => {
|
|||
}));
|
||||
|
||||
let options = [{ label: __("Select DocType"), value: "" }, ...doctypes.value];
|
||||
return { fieldtype: "Select", label: __("Fetch Form"), options };
|
||||
return { fieldtype: "Select", label: __("Fetch From"), options };
|
||||
});
|
||||
|
||||
let field_df = computedAsync(async () => {
|
||||
let options = [{ label: __("Select Field"), value: "" }];
|
||||
let df = { fieldtype: "Select", label: __("Fetch Form"), options };
|
||||
let df = { fieldtype: "Select", label: __("Fetch From"), options };
|
||||
if (!doctype.value) return df;
|
||||
let doctype_name = doctypes.value?.find(df => df.value == doctype.value).doctype_name;
|
||||
if (!doctype_name) return df;
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ import FormBuilderComponent from "./FormBuilder.vue";
|
|||
import { registerGlobalComponents } from "./globals.js";
|
||||
|
||||
class FormBuilder {
|
||||
constructor({ wrapper, page, doctype, customize }) {
|
||||
constructor({ wrapper, frm, doctype, customize }) {
|
||||
this.$wrapper = $(wrapper);
|
||||
this.page = page;
|
||||
this.frm = frm;
|
||||
this.page = frm.page;
|
||||
this.doctype = doctype;
|
||||
this.customize = customize;
|
||||
this.read_only = false;
|
||||
|
|
@ -21,21 +22,14 @@ class FormBuilder {
|
|||
|
||||
this.setup_page_actions();
|
||||
!refresh && this.setup_app();
|
||||
refresh && this.update_store();
|
||||
this.watch_changes();
|
||||
}
|
||||
|
||||
async setup_page_actions() {
|
||||
// clear actions
|
||||
this.page.clear_actions();
|
||||
this.page.clear_menu();
|
||||
this.page.clear_custom_actions();
|
||||
|
||||
// setup page actions
|
||||
this.primary_btn = this.page.set_primary_action(__("Save"), () =>
|
||||
this.store.save_changes()
|
||||
);
|
||||
|
||||
setup_page_actions() {
|
||||
this.preview_btn?.remove();
|
||||
this.preview_btn = this.page.add_button(__("Show Preview"), () => {
|
||||
this.store.frm.layout.tabs.find((tab) => tab.label === "Form").set_active();
|
||||
this.store.preview = !this.store.preview;
|
||||
|
||||
if (this.store.read_only && !this.read_only) {
|
||||
|
|
@ -44,34 +38,10 @@ class FormBuilder {
|
|||
|
||||
this.store.read_only = this.store.preview;
|
||||
this.read_only = true;
|
||||
});
|
||||
|
||||
this.reset_changes_btn = this.page.add_button(__("Reset Changes"), () => {
|
||||
this.store.reset_changes();
|
||||
// toggle preview btn text
|
||||
this.preview_btn.text(this.store.preview ? __("Hide Preview") : __("Show Preview"));
|
||||
});
|
||||
|
||||
this.go_to_doctype_list_btn = this.page.add_button(
|
||||
__("Go to {0} List", [__(this.doctype)]),
|
||||
() => {
|
||||
window.open(`/app/${frappe.router.slug(this.doctype)}`);
|
||||
}
|
||||
);
|
||||
|
||||
this.customize_form_btn = this.page.add_menu_item(__("Switch to Customize"), () => {
|
||||
frappe.set_route("form-builder", this.doctype, "customize");
|
||||
});
|
||||
this.doctype_form_btn = this.page.add_menu_item(__("Switch to DocType"), () => {
|
||||
frappe.set_route("form-builder", this.doctype);
|
||||
});
|
||||
|
||||
this.go_to_doctype_btn = this.page.add_menu_item(__("Go to DocType"), () =>
|
||||
frappe.set_route("Form", "DocType", this.doctype)
|
||||
);
|
||||
this.go_to_customize_form_btn = this.page.add_menu_item(__("Go to Customize Form"), () =>
|
||||
frappe.set_route("Form", "Customize Form", {
|
||||
doc_type: this.doctype,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setup_app() {
|
||||
|
|
@ -85,9 +55,7 @@ class FormBuilder {
|
|||
|
||||
// create a store
|
||||
this.store = useStore();
|
||||
this.store.doctype = this.doctype;
|
||||
this.store.is_customize_form = this.customize;
|
||||
this.store.page = this.page;
|
||||
this.update_store();
|
||||
|
||||
// register global components
|
||||
registerGlobalComponents(app);
|
||||
|
|
@ -96,69 +64,21 @@ class FormBuilder {
|
|||
this.$form_builder = app.mount(this.$wrapper.get(0));
|
||||
}
|
||||
|
||||
update_store() {
|
||||
this.store.doctype = this.doctype;
|
||||
this.store.is_customize_form = this.customize;
|
||||
this.store.page = this.page;
|
||||
this.store.frm = this.frm;
|
||||
}
|
||||
|
||||
watch_changes() {
|
||||
watchEffect(() => {
|
||||
if (this.store.dirty) {
|
||||
this.page.set_indicator(__("Not Saved"), "orange");
|
||||
this.reset_changes_btn.show();
|
||||
if (this.store.dirty || this.frm.is_dirty()) {
|
||||
this.frm.dirty();
|
||||
} else {
|
||||
this.page.clear_indicator();
|
||||
this.reset_changes_btn.hide();
|
||||
}
|
||||
|
||||
// hide all buttons
|
||||
this.go_to_doctype_list_btn.hide();
|
||||
this.customize_form_btn.hide();
|
||||
this.doctype_form_btn.hide();
|
||||
this.go_to_doctype_btn.hide();
|
||||
this.go_to_customize_form_btn.hide();
|
||||
|
||||
this.page.menu_btn_group.show();
|
||||
let hide_menu = true;
|
||||
|
||||
// show customize form & Go to customize form btn
|
||||
if (
|
||||
this.store.doc &&
|
||||
!this.store.doc.custom &&
|
||||
!this.store.doc.issingle &&
|
||||
!this.store.is_customize_form &&
|
||||
!in_list(frappe.model.core_doctypes_list, this.doctype)
|
||||
) {
|
||||
this.customize_form_btn.show();
|
||||
this.go_to_customize_form_btn.show();
|
||||
hide_menu = false;
|
||||
}
|
||||
|
||||
// show doctype form & Go to doctype form btn
|
||||
if (
|
||||
this.store.doc &&
|
||||
!this.store.doc.custom &&
|
||||
!this.store.doc.issingle &&
|
||||
this.store.is_customize_form
|
||||
) {
|
||||
this.doctype_form_btn.show();
|
||||
this.go_to_doctype_btn.show();
|
||||
hide_menu = false;
|
||||
}
|
||||
|
||||
// show Go to {0} List or Go to {0} button
|
||||
if (this.store.doc && !this.store.doc.istable) {
|
||||
let label = this.store.doc.issingle
|
||||
? __("Go to {0}", [__(this.doctype)])
|
||||
: __("Go to {0} List", [__(this.doctype)]);
|
||||
|
||||
this.go_to_doctype_list_btn.text(label).show();
|
||||
}
|
||||
|
||||
if (hide_menu && window.matchMedia("(min-device-width: 992px)").matches) {
|
||||
this.page.menu_btn_group.hide();
|
||||
}
|
||||
|
||||
// toggle preview btn text
|
||||
this.preview_btn.text(this.store.preview ? __("Hide Preview") : __("Show Preview"));
|
||||
|
||||
// toggle primary btn and show indicator based on read_only state
|
||||
this.primary_btn.toggle(!this.store.read_only);
|
||||
if (this.store.read_only) {
|
||||
let message = this.store.preview ? __("Preview Mode") : __("Read Only");
|
||||
this.page.set_indicator(message, "orange");
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useDebouncedRefHistory, onKeyDown } from "@vueuse/core";
|
|||
|
||||
export const useStore = defineStore("form-builder-store", () => {
|
||||
let doctype = ref("");
|
||||
let frm = ref(null);
|
||||
let doc = ref(null);
|
||||
let docfields = ref([]);
|
||||
let custom_docfields = ref([]);
|
||||
|
|
@ -69,17 +70,9 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
}
|
||||
|
||||
async function fetch() {
|
||||
await frappe.model.clear_doc("DocType", doctype.value);
|
||||
await frappe.model.with_doctype(doctype.value);
|
||||
|
||||
if (is_customize_form.value) {
|
||||
await frappe.model.with_doc("Customize Form");
|
||||
let _doc = frappe.get_doc("Customize Form");
|
||||
_doc.doc_type = doctype.value;
|
||||
let r = await frappe.call({ method: "fetch_to_customize", doc: _doc });
|
||||
doc.value = r.docs[0];
|
||||
} else {
|
||||
doc.value = await frappe.db.get_doc("DocType", doctype.value);
|
||||
doc.value = frm.value.doc;
|
||||
if (doctype.value.startsWith("new-doctype-")) {
|
||||
doc.value.fields = [get_df("Data", "", __("Title"))];
|
||||
}
|
||||
|
||||
if (!get_docfields.value.length) {
|
||||
|
|
@ -99,18 +92,19 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
|
||||
nextTick(() => {
|
||||
dirty.value = false;
|
||||
frm.value.doc.__unsaved = 0;
|
||||
frm.value.page.clear_indicator();
|
||||
read_only.value =
|
||||
!is_customize_form.value && !frappe.boot.developer_mode && !doc.value.custom;
|
||||
preview.value = false;
|
||||
});
|
||||
|
||||
setup_undo_redo();
|
||||
setup_breadcrumbs();
|
||||
}
|
||||
|
||||
let undo_redo_keyboard_event = onKeyDown(true, (e) => {
|
||||
if (!ref_history.value) return;
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (frm.value.get_active_tab().label == "Form" && (e.ctrlKey || e.metaKey)) {
|
||||
if (e.key === "z" && !e.shiftKey && ref_history.value.canUndo) {
|
||||
ref_history.value.undo();
|
||||
} else if (e.key === "z" && e.shiftKey && ref_history.value.canRedo) {
|
||||
|
|
@ -125,30 +119,17 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
undo_redo_keyboard_event;
|
||||
}
|
||||
|
||||
function setup_breadcrumbs() {
|
||||
!is_customize_form.value && frappe.model.init_doctype("DocType");
|
||||
let breadcrumbs = `
|
||||
<li><a href="/app/doctype">${__("DocType")}</a></li>
|
||||
<li><a href="/app/doctype/${doctype.value}">${__(doctype.value)}</a></li>
|
||||
`;
|
||||
if (is_customize_form.value) {
|
||||
breadcrumbs = `
|
||||
<li><a href="/app/customize-form?doc_type=${doctype.value}">
|
||||
${__("Customize Form")}
|
||||
</a></li>
|
||||
`;
|
||||
}
|
||||
breadcrumbs += `<li class="disabled"><a href="#">${__("Form Builder")}</a></li>`;
|
||||
frappe.breadcrumbs.clear();
|
||||
frappe.breadcrumbs.$breadcrumbs.append(breadcrumbs);
|
||||
}
|
||||
|
||||
function reset_changes() {
|
||||
fetch();
|
||||
}
|
||||
|
||||
function validate_fields(fields, is_table) {
|
||||
fields = scrub_field_names(fields);
|
||||
let error_message = "";
|
||||
|
||||
let has_fields = fields.some((df) => {
|
||||
return !["Section Break", "Tab Break", "Column Break"].includes(df.fieldtype);
|
||||
});
|
||||
|
||||
if (!has_fields) {
|
||||
error_message = __("DocType must have atleast one field");
|
||||
}
|
||||
|
||||
let not_allowed_in_list_view = ["Attach Image", ...frappe.model.no_value_type];
|
||||
if (is_table) {
|
||||
|
|
@ -168,70 +149,56 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
// check if fieldname already exist
|
||||
let duplicate = fields.filter((f) => f.fieldname == df.fieldname);
|
||||
if (duplicate.length > 1) {
|
||||
frappe.throw(__("Fieldname {0} appears multiple times", get_field_data(df)));
|
||||
error_message = __("Fieldname {0} appears multiple times", get_field_data(df));
|
||||
}
|
||||
|
||||
// Link & Table fields should always have options set
|
||||
if (in_list(["Link", ...frappe.model.table_fields], df.fieldtype) && !df.options) {
|
||||
frappe.throw(
|
||||
__("Options is required for field {0} of type {1}", get_field_data(df))
|
||||
error_message = __(
|
||||
"Options is required for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
);
|
||||
}
|
||||
|
||||
// Do not allow if field is hidden & required but doesn't have default value
|
||||
if (df.hidden && df.reqd && !df.default) {
|
||||
frappe.throw(
|
||||
__(
|
||||
"{0} cannot be hidden and mandatory without any default value",
|
||||
get_field_data(df)
|
||||
)
|
||||
error_message = __(
|
||||
"{0} cannot be hidden and mandatory without any default value",
|
||||
get_field_data(df)
|
||||
);
|
||||
}
|
||||
|
||||
// In List View is not allowed for some fieldtypes
|
||||
if (df.in_list_view && in_list(not_allowed_in_list_view, df.fieldtype)) {
|
||||
frappe.throw(
|
||||
__(
|
||||
"'In List View' is not allowed for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
)
|
||||
error_message = __(
|
||||
"'In List View' is not allowed for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
);
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
frappe.throw(
|
||||
__(
|
||||
"'In Global Search' is not allowed for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
)
|
||||
error_message = __(
|
||||
"'In Global Search' is not allowed for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return error_message;
|
||||
}
|
||||
|
||||
async function save_changes() {
|
||||
if (!dirty.value) {
|
||||
frappe.show_alert({ message: __("No changes to save"), indicator: "orange" });
|
||||
return;
|
||||
}
|
||||
function update_fields() {
|
||||
if (!dirty.value && !frm.value.is_new()) return;
|
||||
|
||||
frappe.dom.freeze(__("Saving..."));
|
||||
|
||||
try {
|
||||
if (is_customize_form.value) {
|
||||
let _doc = frappe.get_doc("Customize Form");
|
||||
_doc.doc_type = doctype.value;
|
||||
_doc.fields = get_updated_fields();
|
||||
validate_fields(_doc.fields, _doc.istable);
|
||||
await frappe.call({ method: "save_customization", doc: _doc });
|
||||
} else {
|
||||
doc.value.fields = get_updated_fields();
|
||||
validate_fields(doc.value.fields, doc.value.istable);
|
||||
await frappe.call("frappe.client.save", { doc: doc.value });
|
||||
frappe.toast("Fields Table Updated");
|
||||
}
|
||||
fetch();
|
||||
let fields = get_updated_fields();
|
||||
let has_error = validate_fields(fields, doc.value.istable);
|
||||
if (has_error) return has_error;
|
||||
doc.value.fields = fields;
|
||||
return fields;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
|
|
@ -242,6 +209,9 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
function get_updated_fields() {
|
||||
let fields = [];
|
||||
let idx = 0;
|
||||
let new_field_name = is_customize_form.value
|
||||
? "new-customize-form-field-"
|
||||
: "new-docfield-";
|
||||
|
||||
let layout_fields = JSON.parse(JSON.stringify(form.value.layout.tabs));
|
||||
|
||||
|
|
@ -252,6 +222,9 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
) {
|
||||
idx++;
|
||||
tab.df.idx = idx;
|
||||
if (tab.df.__unsaved && tab.df.__islocal) {
|
||||
tab.df.name = new_field_name + idx;
|
||||
}
|
||||
fields.push(tab.df);
|
||||
}
|
||||
|
||||
|
|
@ -265,6 +238,9 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
if ((j == 0 && is_df_updated(section.df, get_df("Section Break"))) || j > 0) {
|
||||
idx++;
|
||||
section.df.idx = idx;
|
||||
if (section.df.__unsaved && section.df.__islocal) {
|
||||
section.df.name = new_field_name + idx;
|
||||
}
|
||||
fields.push(section.df);
|
||||
}
|
||||
|
||||
|
|
@ -277,12 +253,18 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
) {
|
||||
idx++;
|
||||
column.df.idx = idx;
|
||||
if (column.df.__unsaved && column.df.__islocal) {
|
||||
column.df.name = new_field_name + idx;
|
||||
}
|
||||
fields.push(column.df);
|
||||
}
|
||||
|
||||
column.fields.forEach((field) => {
|
||||
idx++;
|
||||
field.df.idx = idx;
|
||||
if (field.df.__unsaved && field.df.__islocal) {
|
||||
field.df.name = new_field_name + idx;
|
||||
}
|
||||
fields.push(field.df);
|
||||
section.has_fields = true;
|
||||
});
|
||||
|
|
@ -300,9 +282,11 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
}
|
||||
|
||||
function is_df_updated(df, new_df) {
|
||||
delete df.name;
|
||||
delete new_df.name;
|
||||
return JSON.stringify(df) != JSON.stringify(new_df);
|
||||
let df_copy = JSON.parse(JSON.stringify(df));
|
||||
let new_df_copy = JSON.parse(JSON.stringify(new_df));
|
||||
delete df_copy.name;
|
||||
delete new_df_copy.name;
|
||||
return JSON.stringify(df_copy) != JSON.stringify(new_df_copy);
|
||||
}
|
||||
|
||||
function get_layout() {
|
||||
|
|
@ -311,6 +295,7 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
|
||||
return {
|
||||
doctype,
|
||||
frm,
|
||||
doc,
|
||||
form,
|
||||
dirty,
|
||||
|
|
@ -326,9 +311,8 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
has_standard_field,
|
||||
is_user_generated_field,
|
||||
fetch,
|
||||
reset_changes,
|
||||
validate_fields,
|
||||
save_changes,
|
||||
update_fields,
|
||||
get_updated_fields,
|
||||
is_df_updated,
|
||||
get_layout,
|
||||
|
|
|
|||
|
|
@ -61,8 +61,6 @@ export function create_layout(fields) {
|
|||
if (df.fieldname) {
|
||||
// make a copy to avoid mutation bugs
|
||||
df = JSON.parse(JSON.stringify(df));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (df.fieldtype === "Tab Break") {
|
||||
|
|
@ -71,7 +69,7 @@ export function create_layout(fields) {
|
|||
set_section(df);
|
||||
} else if (df.fieldtype === "Column Break") {
|
||||
set_column(df);
|
||||
} else if (df.name) {
|
||||
} else {
|
||||
if (!column) set_column();
|
||||
|
||||
let field = { df: df };
|
||||
|
|
|
|||
|
|
@ -28,8 +28,11 @@ frappe.assets = {
|
|||
}
|
||||
|
||||
if (localStorage._last_load) {
|
||||
var not_updated_since = new Date() - new Date(localStorage._last_load);
|
||||
if (not_updated_since < 10000 || not_updated_since > 86400000) {
|
||||
let not_updated_since = new Date() - new Date(localStorage._last_load);
|
||||
// Evict cache every 2 days
|
||||
// Evict cache if page is reloaded within 10 seconds. Which could be user trying to
|
||||
// refresh if things feel broken.
|
||||
if ((not_updated_since < 5000 && is_reload()) || not_updated_since > 2 * 86400000) {
|
||||
frappe.assets.clear_local_storage();
|
||||
}
|
||||
} else {
|
||||
|
|
@ -184,3 +187,15 @@ frappe.assets = {
|
|||
return path;
|
||||
},
|
||||
};
|
||||
|
||||
function is_reload() {
|
||||
try {
|
||||
return window.performance
|
||||
?.getEntriesByType("navigation")
|
||||
.map((nav) => nav.type)
|
||||
.includes("reload");
|
||||
} catch (e) {
|
||||
// Safari probably
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ class Picker {
|
|||
}
|
||||
|
||||
setup_hue_event() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let on_drag = (x, y) => {
|
||||
this.hue_selector_position.x = x;
|
||||
this.hue = Math.round((x * 360) / this.hue_map.offsetWidth);
|
||||
|
|
@ -152,9 +151,7 @@ class Picker {
|
|||
}
|
||||
|
||||
get_pointer_coords() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let h, s, v;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
[h, s, v] = utils.get_hsv(this.get_color());
|
||||
let width = this.color_map.offsetWidth;
|
||||
let height = this.color_map.offsetHeight;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ frappe.defaults = {
|
|||
if (!d && frappe.defaults.is_a_user_permission_key(key)) {
|
||||
d = defaults[frappe.model.scrub(key)];
|
||||
// Check for default user permission values
|
||||
user_default = this.get_user_permission_default(key, defaults);
|
||||
let user_default = this.get_user_permission_default(key, defaults);
|
||||
if (user_default) d = user_default;
|
||||
}
|
||||
if ($.isArray(d)) d = d[0];
|
||||
|
|
@ -130,4 +130,12 @@ frappe.defaults = {
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
load_user_permission_from_boot: function () {
|
||||
if (frappe.boot.user.user_permissions) {
|
||||
this._user_permissions = Object.assign({}, frappe.boot.user.user_permissions);
|
||||
} else {
|
||||
frappe.defaults.update_user_permissions();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -151,27 +151,6 @@ frappe.Application = class Application {
|
|||
|
||||
// REDESIGN-TODO: Fix preview popovers
|
||||
this.link_preview = new frappe.ui.LinkPreview();
|
||||
|
||||
if (!frappe.boot.developer_mode) {
|
||||
if (frappe.user.has_role("System Manager")) {
|
||||
setInterval(function () {
|
||||
frappe.call({
|
||||
method: "frappe.core.doctype.log_settings.log_settings.has_unseen_error_log",
|
||||
args: {
|
||||
user: frappe.session.user,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message && r.message.show_alert) {
|
||||
frappe.show_alert({
|
||||
indicator: "red",
|
||||
message: r.message.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}, 600000); // check every 10 minutes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_route() {
|
||||
|
|
@ -298,7 +277,7 @@ frappe.Application = class Application {
|
|||
}
|
||||
|
||||
load_user_permissions() {
|
||||
frappe.defaults.update_user_permissions();
|
||||
frappe.defaults.load_user_permission_from_boot();
|
||||
|
||||
frappe.realtime.on(
|
||||
"update_user_permissions",
|
||||
|
|
|
|||
|
|
@ -134,8 +134,6 @@ frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form.
|
|||
|
||||
setTimeout(() => (this.frm.__from_autoname = false), 500);
|
||||
}
|
||||
|
||||
this.frm.set_df_property("fields", "reqd", this.frm.doc.autoname !== "Prompt");
|
||||
}
|
||||
|
||||
setup_fetch_from_fields(doc, doctype, docname) {
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui
|
|||
|
||||
options = options.map((o) => {
|
||||
if (typeof o !== "string") {
|
||||
o.label = cstr(o.label);
|
||||
o.label = __(cstr(o.label));
|
||||
o.value = cstr(o.value);
|
||||
}
|
||||
return o;
|
||||
|
|
|
|||
|
|
@ -62,25 +62,22 @@ frappe.ui.form.Control = class BaseControl {
|
|||
|
||||
// like in case of a dialog box
|
||||
if (cint(this.df.hidden)) {
|
||||
// eslint-disable-next-line
|
||||
if (explain) console.log("By Hidden: None"); // eslint-disable-line no-console
|
||||
if (explain) console.log("By Hidden: None");
|
||||
return "None";
|
||||
} else if (cint(this.df.hidden_due_to_dependency)) {
|
||||
// eslint-disable-next-line
|
||||
if (explain) console.log("By Hidden Dependency: None"); // eslint-disable-line no-console
|
||||
if (explain) console.log("By Hidden Dependency: None");
|
||||
return "None";
|
||||
} else if (
|
||||
cint(this.df.read_only || this.df.is_virtual || this.df.fieldtype === "Read Only")
|
||||
) {
|
||||
// eslint-disable-next-line
|
||||
if (explain) console.log("By Read Only: Read"); // eslint-disable-line no-console
|
||||
if (explain) console.log("By Read Only: Read");
|
||||
status = "Read";
|
||||
} else if (
|
||||
(this.grid && this.grid.display_status == "Read") ||
|
||||
(this.layout && this.layout.grid && this.layout.grid.display_status == "Read")
|
||||
) {
|
||||
// parent grid is read
|
||||
if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
|
||||
if (explain) console.log("By Parent Grid Read-only: Read");
|
||||
status = "Read";
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +109,7 @@ frappe.ui.form.Control = class BaseControl {
|
|||
var grid = this.grid || this.layout.grid;
|
||||
if (grid.display_status == "Read") {
|
||||
status = "Read";
|
||||
if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
|
||||
if (explain) console.log("By Parent Grid Read-only: Read");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,8 +129,7 @@ frappe.ui.form.Control = class BaseControl {
|
|||
is_null(value) &&
|
||||
!in_list(["HTML", "Image", "Button", "Geolocation"], this.df.fieldtype)
|
||||
) {
|
||||
// eslint-disable-next-line
|
||||
if (explain) console.log("By Hide Read-only, null fields: None"); // eslint-disable-line no-console
|
||||
if (explain) console.log("By Hide Read-only, null fields: None");
|
||||
status = "None";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -165,7 +165,6 @@ frappe.ui.form.ControlCode = class ControlCode extends frappe.ui.form.ControlTex
|
|||
|
||||
const valid_languages = Object.keys(language_map);
|
||||
if (language && !valid_languages.includes(language)) {
|
||||
// eslint-disable-next-line
|
||||
console.warn(
|
||||
`Invalid language option provided for field "${
|
||||
this.df.label
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ frappe.ui.form.make_control = function (opts) {
|
|||
if (frappe.ui.form[control_class_name]) {
|
||||
return new frappe.ui.form[control_class_name](opts);
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
console.log("Invalid Control Name: " + opts.df.fieldtype);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -591,13 +591,19 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
|
|||
let field_value = "";
|
||||
for (const [target_field, source_field] of Object.entries(fetch_map)) {
|
||||
if (value) field_value = response[source_field];
|
||||
frappe.model.set_value(
|
||||
df.parent,
|
||||
docname,
|
||||
target_field,
|
||||
field_value,
|
||||
df.fieldtype
|
||||
);
|
||||
let target_df = frappe.meta.get_docfield(df.parent, target_field);
|
||||
let target_value = frappe.model.get_value(df.parent, docname, target_field);
|
||||
if (target_df?.fetch_if_empty && target_value) {
|
||||
continue;
|
||||
} else {
|
||||
frappe.model.set_value(
|
||||
df.parent,
|
||||
docname,
|
||||
target_field,
|
||||
field_value,
|
||||
df.fieldtype
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable */
|
||||
import Quill from "quill";
|
||||
|
||||
const Embed = Quill.import("blots/embed");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable */
|
||||
const Keys = {
|
||||
TAB: "Tab",
|
||||
ENTER: "Enter",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable */
|
||||
import Quill from "quill";
|
||||
import Keys from "./constants/keys";
|
||||
import "./blots/mention";
|
||||
|
|
|
|||
|
|
@ -77,9 +77,12 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
// wrapper
|
||||
this.wrapper = this.parent;
|
||||
this.$wrapper = $(this.wrapper);
|
||||
|
||||
let is_single_column = this.doctype === "DocType" ? true : this.meta.hide_toolbar;
|
||||
|
||||
frappe.ui.make_app_page({
|
||||
parent: this.wrapper,
|
||||
single_column: this.meta.hide_toolbar,
|
||||
single_column: is_single_column,
|
||||
});
|
||||
this.page = this.wrapper.page;
|
||||
this.layout_main = this.page.main.get(0);
|
||||
|
|
@ -157,12 +160,14 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
action: () => this.undo_manager.undo(),
|
||||
page: this.page,
|
||||
description: __("Undo last action"),
|
||||
condition: () => !this.is_form_builder(),
|
||||
});
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut: "shift+ctrl+z",
|
||||
action: () => this.undo_manager.redo(),
|
||||
page: this.page,
|
||||
description: __("Redo last action"),
|
||||
condition: () => !this.is_form_builder(),
|
||||
});
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut: "ctrl+y",
|
||||
|
|
@ -747,7 +752,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
me.show_success_action();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e); // eslint-disable-line
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1361,6 +1366,13 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
return this.doc.__islocal;
|
||||
}
|
||||
|
||||
is_form_builder() {
|
||||
return (
|
||||
in_list(["DocType", "Customize Form"], this.doctype) &&
|
||||
this.get_active_tab().label == "Form"
|
||||
);
|
||||
}
|
||||
|
||||
get_perm(permlevel, access_type) {
|
||||
return this.perm[permlevel] ? this.perm[permlevel][access_type] : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -441,12 +441,13 @@ export default class Grid {
|
|||
if (d.name === undefined) {
|
||||
d.name = "row " + d.idx;
|
||||
}
|
||||
let grid_row;
|
||||
if (this.grid_rows[ri] && !append_row) {
|
||||
var grid_row = this.grid_rows[ri];
|
||||
grid_row = this.grid_rows[ri];
|
||||
grid_row.doc = d;
|
||||
grid_row.refresh();
|
||||
} else {
|
||||
var grid_row = new GridRow({
|
||||
grid_row = new GridRow({
|
||||
parent: $rows,
|
||||
parent_df: this.df,
|
||||
docfields: this.docfields,
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ export default class GridRow {
|
|||
])
|
||||
.catch((e) => {
|
||||
// aborted
|
||||
console.trace(e); // eslint-disable-line
|
||||
console.trace(e);
|
||||
});
|
||||
} else {
|
||||
let data = null;
|
||||
|
|
|
|||
|
|
@ -240,7 +240,6 @@ frappe.ui.form.Layout = class Layout {
|
|||
}
|
||||
|
||||
make_page(df) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
let me = this;
|
||||
let head = $(`
|
||||
<div class="form-clickable-section text-center">
|
||||
|
|
@ -365,7 +364,10 @@ frappe.ui.form.Layout = class Layout {
|
|||
const section = $(this).removeClass("empty-section visible-section");
|
||||
if (section.find(".frappe-control:not(.hide-control)").length) {
|
||||
section.addClass("visible-section");
|
||||
} else {
|
||||
} else if (
|
||||
section.parent().hasClass("tab-pane") ||
|
||||
section.parent().hasClass("form-page")
|
||||
) {
|
||||
// nothing visible, hide the section
|
||||
section.addClass("empty-section");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -602,7 +602,6 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
|
|||
async get_filtered_parents_for_child_search() {
|
||||
const parent_search_args = this.get_args_for_search();
|
||||
parent_search_args.filter_fields = ["name"];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [response, _] = await this.perform_search(parent_search_args);
|
||||
|
||||
let parent_names = [];
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
|
||||
if (error_fields.length) {
|
||||
let meta = frappe.get_meta(doc.doctype);
|
||||
let message;
|
||||
if (meta.istable) {
|
||||
const table_field = frappe.meta.docfield_map[doc.parenttype][doc.parentfield];
|
||||
|
||||
|
|
@ -163,12 +164,12 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
table_field.label || frappe.unscrub(table_field.fieldname)
|
||||
).bold();
|
||||
|
||||
var message = __("Mandatory fields required in table {0}, Row {1}", [
|
||||
message = __("Mandatory fields required in table {0}, Row {1}", [
|
||||
table_label,
|
||||
doc.idx,
|
||||
]);
|
||||
} else {
|
||||
var message = __("Mandatory fields required in {0}", [__(doc.doctype)]);
|
||||
message = __("Mandatory fields required in {0}", [__(doc.doctype)]);
|
||||
}
|
||||
message = message + "<br><br><ul><li>" + error_fields.join("</li><li>") + "</ul>";
|
||||
frappe.msgprint({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
/* eslint-disable no-console */
|
||||
|
||||
window.refresh_many = function (flist, dn, table_field) {
|
||||
for (var i in flist) {
|
||||
|
|
|
|||
|
|
@ -213,12 +213,7 @@ frappe.ui.form.ScriptManager = class ScriptManager {
|
|||
df.read_only == 1 ||
|
||||
df.is_virtual == 1;
|
||||
|
||||
if (
|
||||
is_read_only_field &&
|
||||
df.fetch_from &&
|
||||
(!df.fetch_if_empty || (df.fetch_if_empty && !me.frm.doc[df.fieldname])) &&
|
||||
df.fetch_from.indexOf(".") != -1
|
||||
) {
|
||||
if (is_read_only_field && df.fetch_from && df.fetch_from.indexOf(".") != -1) {
|
||||
var parts = df.fetch_from.split(".");
|
||||
me.frm.add_fetch(parts[0], parts[1], df.fieldname, df.parent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@ frappe.ui.form.Toolbar = class Toolbar {
|
|||
}
|
||||
}
|
||||
set_title() {
|
||||
let title;
|
||||
if (this.frm.is_new()) {
|
||||
var title = __("New {0}", [__(this.frm.meta.name)]);
|
||||
title = __("New {0}", [__(this.frm.meta.name)]);
|
||||
} else if (this.frm.meta.title_field) {
|
||||
let title_field = (this.frm.doc[this.frm.meta.title_field] || "").toString().trim();
|
||||
var title = strip_html(title_field || this.frm.docname);
|
||||
title = strip_html(title_field || this.frm.docname);
|
||||
if (
|
||||
this.frm.doc.__islocal ||
|
||||
title === this.frm.docname ||
|
||||
|
|
@ -51,7 +52,7 @@ frappe.ui.form.Toolbar = class Toolbar {
|
|||
});
|
||||
}
|
||||
} else {
|
||||
var title = this.frm.docname;
|
||||
title = this.frm.docname;
|
||||
}
|
||||
|
||||
var me = this;
|
||||
|
|
|
|||
|
|
@ -668,7 +668,6 @@ class FilterArea {
|
|||
const fields_dict = this.list_view.page.fields_dict;
|
||||
|
||||
let out = filters.reduce((out, filter) => {
|
||||
// eslint-disable-next-line
|
||||
const [dt, fieldname, condition, value] = filter;
|
||||
out.promise = out.promise || Promise.resolve();
|
||||
out.non_standard_filters = out.non_standard_filters || [];
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
) {
|
||||
this.sidebar.find(".list-tags").remove();
|
||||
} else {
|
||||
this.sidebar.find(".list-stats").on("click", (e) => {
|
||||
this.sidebar.find(".list-stats").on("show.bs.dropdown", (e) => {
|
||||
this.reload_stats();
|
||||
});
|
||||
}
|
||||
|
|
@ -187,6 +187,10 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
|
||||
get_stats() {
|
||||
var me = this;
|
||||
|
||||
let dropdown_options = me.sidebar.find(".list-stats-dropdown .stat-result");
|
||||
this.set_loading_state(dropdown_options);
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.desk.reportview.get_sidebar_stats",
|
||||
type: "GET",
|
||||
|
|
@ -208,6 +212,14 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
});
|
||||
}
|
||||
|
||||
set_loading_state(dropdown) {
|
||||
dropdown.html(`<li>
|
||||
<div class="empty-state">
|
||||
${__("Loading...")}
|
||||
</div>
|
||||
</li>`);
|
||||
}
|
||||
|
||||
render_stat(stats) {
|
||||
let args = {
|
||||
stats: stats,
|
||||
|
|
|
|||
|
|
@ -587,9 +587,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
</div>
|
||||
`);
|
||||
this.setup_new_doc_event();
|
||||
if (this.list_view_settings && !this.list_view_settings.disable_sidebar_stats) {
|
||||
this.list_sidebar && this.list_sidebar.reload_stats();
|
||||
}
|
||||
this.toggle_paging && this.$paging_area.toggle(true);
|
||||
}
|
||||
|
||||
|
|
@ -712,9 +709,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
get_column_html(col, doc) {
|
||||
if (col.type === "Status" || col.df?.options == "Workflow State") {
|
||||
let show_workflow_state = col.df?.options == "Workflow State";
|
||||
return `
|
||||
<div class="list-row-col hidden-xs ellipsis">
|
||||
${this.get_indicator_html(doc)}
|
||||
${this.get_indicator_html(doc, show_workflow_state)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -1014,8 +1012,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
return subject_html;
|
||||
}
|
||||
|
||||
get_indicator_html(doc) {
|
||||
const indicator = frappe.get_indicator(doc, this.doctype);
|
||||
get_indicator_html(doc, show_workflow_state) {
|
||||
const indicator = frappe.get_indicator(doc, this.doctype, show_workflow_state);
|
||||
// sequence is important
|
||||
const docstatus_description = [
|
||||
__("Document is in draft state"),
|
||||
|
|
@ -1544,26 +1542,28 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
get_url_with_filters() {
|
||||
const query_params = this.get_filters_for_args()
|
||||
.map((filter) => {
|
||||
if (filter[2] === "=") {
|
||||
return `${filter[1]}=${encodeURIComponent(filter[3])}`;
|
||||
}
|
||||
return [
|
||||
filter[1],
|
||||
"=",
|
||||
encodeURIComponent(JSON.stringify([filter[2], filter[3]])),
|
||||
].join("");
|
||||
})
|
||||
.join("&");
|
||||
let search_params = this.get_search_params();
|
||||
|
||||
let full_url = window.location.href.replace(window.location.search, "");
|
||||
if (query_params) {
|
||||
full_url += "?" + query_params;
|
||||
if (search_params.size) {
|
||||
full_url += "?" + search_params.toString();
|
||||
}
|
||||
return full_url;
|
||||
}
|
||||
|
||||
get_search_params() {
|
||||
let search_params = new URLSearchParams();
|
||||
|
||||
this.get_filters_for_args().forEach((filter) => {
|
||||
if (filter[2] === "=") {
|
||||
search_params.append(filter[1], filter[3]);
|
||||
} else {
|
||||
search_params.append(filter[1], JSON.stringify([filter[2], filter[3]]));
|
||||
}
|
||||
});
|
||||
return search_params;
|
||||
}
|
||||
|
||||
get_menu_items() {
|
||||
const doctype = this.doctype;
|
||||
const items = [];
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// Adapted from John Resig - http://ejohn.org/ - MIT Licensed
|
||||
|
||||
frappe.template = { compiled: {}, debug: {} };
|
||||
|
||||
/* eslint-disable */
|
||||
frappe.template.compile = function (str, name) {
|
||||
var key = name || str;
|
||||
|
||||
|
|
@ -96,14 +98,17 @@ frappe.template.compile = function (str, name) {
|
|||
|
||||
return frappe.template.compiled[key];
|
||||
};
|
||||
/* eslint-enable */
|
||||
|
||||
frappe.render = function (str, data, name) {
|
||||
return frappe.template.compile(str, name)(data);
|
||||
};
|
||||
frappe.render_template = function (name, data) {
|
||||
let template;
|
||||
if (name.indexOf(" ") !== -1) {
|
||||
var template = name;
|
||||
template = name;
|
||||
} else {
|
||||
var template = frappe.templates[name];
|
||||
template = frappe.templates[name];
|
||||
}
|
||||
if (data === undefined) {
|
||||
data = {};
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ frappe.has_indicator = function (doctype) {
|
|||
return false;
|
||||
};
|
||||
|
||||
frappe.get_indicator = function (doc, doctype) {
|
||||
frappe.get_indicator = function (doc, doctype, show_workflow_state) {
|
||||
if (doc.__unsaved) {
|
||||
return [__("Not Saved"), "orange"];
|
||||
}
|
||||
|
|
@ -40,13 +40,13 @@ frappe.get_indicator = function (doc, doctype) {
|
|||
workflow_fieldname = frappe.workflow.get_state_fieldname(doctype);
|
||||
|
||||
// workflow
|
||||
if (workflow_fieldname && !without_workflow) {
|
||||
if (workflow_fieldname && (!without_workflow || show_workflow_state)) {
|
||||
var value = doc[workflow_fieldname];
|
||||
if (value) {
|
||||
var colour = "";
|
||||
let colour = "";
|
||||
|
||||
if (locals["Workflow State"][value] && locals["Workflow State"][value].style) {
|
||||
var colour = {
|
||||
colour = {
|
||||
Success: "green",
|
||||
Warning: "orange",
|
||||
Danger: "red",
|
||||
|
|
|
|||
|
|
@ -173,7 +173,6 @@ $.extend(frappe.meta, {
|
|||
});
|
||||
|
||||
if (!out) {
|
||||
// eslint-disable-next-line
|
||||
console.log(
|
||||
__("Warning: Unable to find {0} in any table related to {1}", [
|
||||
key,
|
||||
|
|
|
|||
|
|
@ -462,8 +462,9 @@ $.extend(frappe.model, {
|
|||
var val = locals[dt] && locals[dt][dn] && locals[dt][dn][fn];
|
||||
var df = frappe.meta.get_docfield(dt, fn, dn);
|
||||
|
||||
let ret;
|
||||
if (frappe.model.table_fields.includes(df.fieldtype)) {
|
||||
var ret = false;
|
||||
ret = false;
|
||||
$.each(locals[df.options] || {}, function (k, d) {
|
||||
if (d.parent == dn && d.parenttype == dt && d.parentfield == df.fieldname) {
|
||||
ret = true;
|
||||
|
|
@ -471,7 +472,7 @@ $.extend(frappe.model, {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
var ret = !is_null(val);
|
||||
ret = !is_null(val);
|
||||
}
|
||||
return ret ? true : false;
|
||||
},
|
||||
|
|
@ -616,12 +617,13 @@ $.extend(frappe.model, {
|
|||
},
|
||||
|
||||
get_children: function (doctype, parent, parentfield, filters) {
|
||||
let doc;
|
||||
if ($.isPlainObject(doctype)) {
|
||||
var doc = doctype;
|
||||
var filters = parentfield;
|
||||
var parentfield = parent;
|
||||
doc = doctype;
|
||||
filters = parentfield;
|
||||
parentfield = parent;
|
||||
} else {
|
||||
var doc = frappe.get_doc(doctype, parent);
|
||||
doc = frappe.get_doc(doctype, parent);
|
||||
}
|
||||
|
||||
var children = doc[parentfield] || [];
|
||||
|
|
@ -652,8 +654,8 @@ $.extend(frappe.model, {
|
|||
|
||||
var parent = null;
|
||||
if (doc.parenttype) {
|
||||
var parent = doc.parent,
|
||||
parenttype = doc.parenttype,
|
||||
parent = doc.parent;
|
||||
var parenttype = doc.parenttype,
|
||||
parentfield = doc.parentfield;
|
||||
}
|
||||
delete locals[doctype][name];
|
||||
|
|
|
|||
|
|
@ -294,8 +294,8 @@ frappe.request.call = function (opts) {
|
|||
status_code_handler(data, xhr);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Unable to handle success response", data); // eslint-disable-line
|
||||
console.error(e); // eslint-disable-line
|
||||
console.log("Unable to handle success response", data);
|
||||
console.error(e);
|
||||
}
|
||||
})
|
||||
.always(function (data, textStatus, xhr) {
|
||||
|
|
@ -304,7 +304,7 @@ frappe.request.call = function (opts) {
|
|||
data = JSON.parse(data);
|
||||
}
|
||||
if (data.responseText) {
|
||||
var xhr = data;
|
||||
var xhr = data; // eslint-disable-line
|
||||
data = JSON.parse(data.responseText);
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -348,8 +348,8 @@ frappe.request.call = function (opts) {
|
|||
// if not handled by error handler!
|
||||
opts.error_callback && opts.error_callback(xhr);
|
||||
} catch (e) {
|
||||
console.log("Unable to handle failed response"); // eslint-disable-line
|
||||
console.error(e); // eslint-disable-line
|
||||
console.log("Unable to handle failed response");
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -366,7 +366,6 @@ frappe.request.is_fresh = function (args, threshold) {
|
|||
new Date() - past_request.timestamp < threshold &&
|
||||
frappe.utils.deep_equal(args, past_request.args)
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("throttled");
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ frappe.ui.Scanner = class Scanner {
|
|||
try {
|
||||
this.options.on_scan(decodedResult);
|
||||
} catch (error) {
|
||||
console.error(error); // eslint-disable-line
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
if (!this.options.multiple) {
|
||||
|
|
@ -46,14 +46,13 @@ frappe.ui.Scanner = class Scanner {
|
|||
}
|
||||
},
|
||||
(errorMessage) => {
|
||||
// eslint-disable-line
|
||||
// parse error, ignore it.
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
this.is_alive = false;
|
||||
this.hide_dialog();
|
||||
console.error(err); // eslint-disable-line
|
||||
console.error(err);
|
||||
});
|
||||
this.is_alive = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ frappe.ui.color = {
|
|||
if (color_names.includes(color_name)) {
|
||||
return frappe.ui.color_map[color_name];
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
console.warn(`'color_name' can be one of ${color_names} and not ${color_name}`);
|
||||
}
|
||||
},
|
||||
|
|
@ -52,7 +51,6 @@ frappe.ui.color = {
|
|||
const color = this.get_color(color_name);
|
||||
return color ? color[shades[shade]] : color_name;
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
console.warn(`'shade' can be one of ${Object.keys(shades)} and not ${shade}`);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue