Merge branch 'develop' into bump-pydantic-v2
This commit is contained in:
commit
faab26ce4f
271 changed files with 3248 additions and 3031 deletions
|
|
@ -37,3 +37,6 @@ c0c5b2ebdddbe8898ce2d5e5365f4931ff73b6bf
|
|||
|
||||
# minor formatting fix in `user.py`
|
||||
f223bc02490902dfcc32892058f13f343d51fbaf
|
||||
|
||||
# frappe.cache() -> frappe.cache
|
||||
fa6dc03cc87ad74e11609e7373078366fdcb3e1b
|
||||
|
|
|
|||
4
.github/helper/install.sh
vendored
4
.github/helper/install.sh
vendored
|
|
@ -54,7 +54,9 @@ fi
|
|||
|
||||
echo "Starting Bench..."
|
||||
|
||||
bench start &> bench_start.log &
|
||||
export FRAPPE_TUNE_GC=True
|
||||
|
||||
bench start &> ~/frappe-bench/bench_start.log &
|
||||
|
||||
if [ "$TYPE" == "server" ]
|
||||
then
|
||||
|
|
|
|||
2
.github/workflows/linters.yml
vendored
2
.github/workflows/linters.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
fetch-depth: 200
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
- name: Check commit titles
|
||||
|
|
|
|||
2
.github/workflows/on_release.yml
vendored
2
.github/workflows/on_release.yml
vendored
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
|
|
|
|||
38
.github/workflows/patch-mariadb-tests.yml
vendored
38
.github/workflows/patch-mariadb-tests.yml
vendored
|
|
@ -62,14 +62,16 @@ jobs:
|
|||
fi
|
||||
|
||||
- name: Setup Python
|
||||
uses: "gabrielfalcao/pyenv-action@v10"
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
versions: 3.10:latest, 3.7:latest
|
||||
python-version: |
|
||||
3.7
|
||||
3.10
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
|
|
@ -100,7 +102,6 @@ jobs:
|
|||
run: |
|
||||
bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
|
||||
pip install frappe-bench
|
||||
pyenv global $(pyenv versions | grep '3.10')
|
||||
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
env:
|
||||
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
|
||||
|
|
@ -120,26 +121,39 @@ jobs:
|
|||
|
||||
function update_to_version() {
|
||||
version=$1
|
||||
py=$2
|
||||
|
||||
branch_name="version-$version-hotfix"
|
||||
echo "Updating to v$version"
|
||||
git fetch --depth 1 upstream $branch_name:$branch_name
|
||||
git checkout -q -f $branch_name
|
||||
pip install -U frappe-bench
|
||||
|
||||
pgrep honcho | xargs kill
|
||||
rm -rf ~/frappe-bench/env
|
||||
bench -v setup env
|
||||
bench -v setup env --python $py
|
||||
bench start &> ~/frappe-bench/bench_start.log &
|
||||
|
||||
bench --site test_site migrate
|
||||
}
|
||||
|
||||
pyenv global $(pyenv versions | grep '3.7')
|
||||
update_to_version 12
|
||||
update_to_version 13
|
||||
update_to_version 12 python3.7
|
||||
update_to_version 13 python3.7
|
||||
|
||||
pyenv global $(pyenv versions | grep '3.10')
|
||||
update_to_version 14
|
||||
update_to_version 14 python3.10
|
||||
|
||||
echo "Updating to last commit"
|
||||
git checkout -q -f "$GITHUB_SHA"
|
||||
rm -rf ~/frappe-bench/env
|
||||
git checkout -q -f "$GITHUB_SHA"
|
||||
bench -v setup env
|
||||
bench --site test_site migrate
|
||||
|
||||
- name: Show bench output
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
cd ~/frappe-bench
|
||||
cat bench_start.log || true
|
||||
cd logs
|
||||
for f in ./*.log*; do
|
||||
echo "Printing log: $f";
|
||||
cat $f
|
||||
done
|
||||
|
|
|
|||
2
.github/workflows/publish-assets-develop.yml
vendored
2
.github/workflows/publish-assets-develop.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
path: 'frappe'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
|
|
|||
6
.github/workflows/server-tests.yml
vendored
6
.github/workflows/server-tests.yml
vendored
|
|
@ -90,7 +90,7 @@ jobs:
|
|||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
|
|
@ -136,6 +136,10 @@ jobs:
|
|||
BUILD_NUMBER: ${{ matrix.container }}
|
||||
TOTAL_BUILDS: 2
|
||||
|
||||
- name: Show bench output
|
||||
if: ${{ always() }}
|
||||
run: cat ~/frappe-bench/bench_start.log || true
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/ui-tests.yml
vendored
2
.github/workflows/ui-tests.yml
vendored
|
|
@ -78,7 +78,7 @@ jobs:
|
|||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
|
|
|
|||
0
.semgrepignore
Normal file
0
.semgrepignore
Normal file
|
|
@ -8,6 +8,8 @@ module.exports = defineConfig({
|
|||
pageLoadTimeout: 15000,
|
||||
video: true,
|
||||
videoUploadOnPasses: false,
|
||||
viewportHeight: 960,
|
||||
viewportWidth: 1400,
|
||||
retries: {
|
||||
runMode: 2,
|
||||
openMode: 2,
|
||||
|
|
|
|||
|
|
@ -7,50 +7,41 @@ context("Awesome Bar", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
cy.get(".navbar .navbar-home").click();
|
||||
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").clear();
|
||||
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").as("awesome_bar");
|
||||
cy.get("@awesome_bar").type("{selectall}");
|
||||
});
|
||||
|
||||
it("navigates to doctype list", () => {
|
||||
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type("todo", {
|
||||
delay: 700,
|
||||
});
|
||||
cy.get("@awesome_bar").type("todo");
|
||||
cy.wait(100);
|
||||
cy.get(".awesomplete").findByRole("listbox").should("be.visible");
|
||||
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type("{enter}", {
|
||||
delay: 700,
|
||||
});
|
||||
|
||||
cy.get("@awesome_bar").type("{enter}");
|
||||
cy.get(".title-text").should("contain", "To Do");
|
||||
|
||||
cy.location("pathname").should("eq", "/app/todo");
|
||||
});
|
||||
|
||||
it("find text in doctype list", () => {
|
||||
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type(
|
||||
"test in todo{enter}",
|
||||
{ delay: 700 }
|
||||
);
|
||||
|
||||
cy.get("@awesome_bar").type("test in todo");
|
||||
cy.wait(100);
|
||||
cy.get("@awesome_bar").type("{enter}");
|
||||
cy.get(".title-text").should("contain", "To Do");
|
||||
|
||||
cy.findByPlaceholderText("ID").should("have.value", "%test%");
|
||||
cy.wait(200);
|
||||
const name_filter = cy.findByPlaceholderText("ID");
|
||||
name_filter.should("have.value", "%test%");
|
||||
cy.clear_filters();
|
||||
});
|
||||
|
||||
it("navigates to new form", () => {
|
||||
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type(
|
||||
"new blog post{enter}",
|
||||
{ delay: 700 }
|
||||
);
|
||||
|
||||
cy.get("@awesome_bar").type("new blog post");
|
||||
cy.wait(100);
|
||||
cy.get("@awesome_bar").type("{enter}");
|
||||
cy.get(".title-text:visible").should("have.text", "New Blog Post");
|
||||
});
|
||||
|
||||
it("calculates math expressions", () => {
|
||||
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type(
|
||||
"55 + 32{downarrow}{enter}",
|
||||
{ delay: 700 }
|
||||
);
|
||||
|
||||
cy.get("@awesome_bar").type("55 + 32");
|
||||
cy.wait(100);
|
||||
cy.get("@awesome_bar").type("{downarrow}{enter}");
|
||||
cy.get(".modal-title").should("contain", "Result");
|
||||
cy.get(".msgprint").should("contain", "55 + 32 = 87");
|
||||
});
|
||||
|
|
|
|||
74
cypress/integration/control_currency.js
Normal file
74
cypress/integration/control_currency.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
context("Control Currency", () => {
|
||||
const fieldname = "currency_field";
|
||||
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit("/app/website");
|
||||
});
|
||||
|
||||
function get_dialog_with_currency(df_options = {}) {
|
||||
return cy.dialog({
|
||||
title: "Currency Check",
|
||||
fields: [
|
||||
{
|
||||
fieldname: fieldname,
|
||||
fieldtype: "Currency",
|
||||
Label: "Currency",
|
||||
...df_options,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
it("check value changes", () => {
|
||||
const TEST_CASES = [
|
||||
{
|
||||
input: "10.101",
|
||||
df_options: { precision: 1 },
|
||||
blur_expected: "10.1",
|
||||
},
|
||||
{
|
||||
input: "10.101",
|
||||
df_options: { precision: "3" },
|
||||
blur_expected: "10.101",
|
||||
},
|
||||
{
|
||||
input: "10.101",
|
||||
df_options: { precision: "" }, // default assumed to be 2;
|
||||
blur_expected: "10.10",
|
||||
},
|
||||
{
|
||||
input: "10.101",
|
||||
df_options: { precision: "0" },
|
||||
blur_expected: "10",
|
||||
},
|
||||
{
|
||||
input: "10.101",
|
||||
df_options: { precision: 0 },
|
||||
blur_expected: "10",
|
||||
},
|
||||
{
|
||||
input: "10.101",
|
||||
df_options: { precision: "" },
|
||||
blur_expected: "10.1",
|
||||
default_precision: 1,
|
||||
},
|
||||
];
|
||||
|
||||
TEST_CASES.forEach((test_case) => {
|
||||
cy.window()
|
||||
.its("frappe")
|
||||
.then((frappe) => {
|
||||
frappe.boot.sysdefaults.currency = test_case.currency;
|
||||
frappe.boot.sysdefaults.currency_precision = test_case.default_precision ?? 2;
|
||||
});
|
||||
|
||||
get_dialog_with_currency(test_case.df_options).as("dialog");
|
||||
cy.get_field(fieldname, "Currency").clear();
|
||||
cy.wait(300);
|
||||
cy.fill_field(fieldname, test_case.input, "Currency").blur();
|
||||
cy.get_field(fieldname, "Currency").should("have.value", test_case.blur_expected);
|
||||
cy.hide_dialog();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -42,14 +42,14 @@ context("Control Icon", () => {
|
|||
|
||||
it("search for icon and clear search input", () => {
|
||||
let search_text = "ed";
|
||||
cy.get(".icon-picker").findByRole("searchbox").click().type(search_text);
|
||||
cy.get(".icon-picker").get(".search-icons > input").click().type(search_text);
|
||||
cy.get(".icon-section .icon-wrapper:not(.hidden)").then((i) => {
|
||||
cy.get(`.icon-section .icon-wrapper[id*='${search_text}']`).then((icons) => {
|
||||
expect(i.length).to.equal(icons.length);
|
||||
});
|
||||
});
|
||||
|
||||
cy.get(".icon-picker").findByRole("searchbox").clear().blur();
|
||||
cy.get(".icon-picker").get(".search-icons > input").clear().blur();
|
||||
cy.get(".icon-section .icon-wrapper").should("not.have.class", "hidden");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -133,8 +133,7 @@ context("Control Link", () => {
|
|||
true
|
||||
);
|
||||
|
||||
cy.clear_cache();
|
||||
cy.wait(500);
|
||||
cy.reload();
|
||||
|
||||
get_dialog_with_link().as("dialog");
|
||||
cy.window()
|
||||
|
|
@ -177,7 +176,7 @@ context("Control Link", () => {
|
|||
cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link");
|
||||
|
||||
cy.get(".frappe-control[data-fieldname=assigned_by] input").focus().as("input");
|
||||
cy.get("@input").type(cy.config("testUser"), { delay: 100 }).blur();
|
||||
cy.get("@input").clear().type(cy.config("testUser"), { delay: 300 }).blur();
|
||||
cy.wait("@validate_link");
|
||||
cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
|
||||
"contain",
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ context("Control Phone", () => {
|
|||
it("case insensitive search for country and clear search", () => {
|
||||
let search_text = "india";
|
||||
cy.get(".selected-phone").click().first();
|
||||
cy.get(".phone-picker").findByRole("searchbox").click().type(search_text);
|
||||
cy.get(".phone-picker").get(".search-phones").click().type(search_text);
|
||||
cy.get(".phone-section .phone-wrapper:not(.hidden)").then((i) => {
|
||||
cy.get(`.phone-section .phone-wrapper[id*="${search_text.toLowerCase()}"]`).then(
|
||||
(countries) => {
|
||||
|
|
@ -56,7 +56,7 @@ context("Control Phone", () => {
|
|||
);
|
||||
});
|
||||
|
||||
cy.get(".phone-picker").findByRole("searchbox").clear().blur();
|
||||
cy.get(".phone-picker").get(".search-phones").clear();
|
||||
cy.get(".phone-section .phone-wrapper").should("not.have.class", "hidden");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ context("Folder Navigation", () => {
|
|||
cy.click_filter_button();
|
||||
cy.get(".filter-action-buttons > .text-muted").findByText("+ Add a Filter").click();
|
||||
cy.get(".fieldname-select-area > .awesomplete > .form-control:last").type("Fol{enter}");
|
||||
cy.get(
|
||||
".filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback"
|
||||
).type("Home{enter}");
|
||||
cy.get(".filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback")
|
||||
.first()
|
||||
.type("Home{enter}");
|
||||
cy.get(".filter-action-buttons > div > .btn-primary").findByText("Apply Filters").click();
|
||||
|
||||
//Adding folder (Test Folder)
|
||||
|
|
@ -24,6 +24,7 @@ context("Folder Navigation", () => {
|
|||
|
||||
it("Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct", () => {
|
||||
//Navigating inside the Attachments folder
|
||||
cy.clear_filters();
|
||||
cy.wait(500);
|
||||
cy.get('[title="Attachments"] > span').click();
|
||||
|
||||
|
|
|
|||
|
|
@ -59,11 +59,13 @@ context("Form", () => {
|
|||
.blur();
|
||||
cy.click_listview_row_item_with_text("Test Form Contact 3");
|
||||
|
||||
cy.scrollTo(0);
|
||||
cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist");
|
||||
cy.get(".prev-doc").should("be.visible").click();
|
||||
cy.get(".msgprint-dialog .modal-body").contains("No further records").should("be.visible");
|
||||
cy.hide_dialog();
|
||||
|
||||
cy.scrollTo(0);
|
||||
cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist");
|
||||
cy.get(".next-doc").should("be.visible").click();
|
||||
cy.get(".msgprint-dialog .modal-body").contains("No further records").should("be.visible");
|
||||
|
|
|
|||
|
|
@ -100,15 +100,15 @@ context("Kanban Board", () => {
|
|||
it("Checks if Kanban Board edits are blocked for non-System Manager and non-owner of the Board", () => {
|
||||
cy.switch_to_user("Administrator");
|
||||
|
||||
const noSystemManager = "nosysmanager@example.com";
|
||||
const not_system_manager = "nosysmanager@example.com";
|
||||
cy.call("frappe.tests.ui_test_helpers.create_test_user", {
|
||||
username: noSystemManager,
|
||||
username: not_system_manager,
|
||||
});
|
||||
cy.remove_role(noSystemManager, "System Manager");
|
||||
cy.remove_role(not_system_manager, "System Manager");
|
||||
cy.call("frappe.tests.ui_test_helpers.create_todo", { description: "Frappe User ToDo" });
|
||||
cy.call("frappe.tests.ui_test_helpers.create_admin_kanban");
|
||||
|
||||
cy.switch_to_user(noSystemManager);
|
||||
cy.switch_to_user(not_system_manager);
|
||||
|
||||
cy.visit("/app/todo/view/kanban/Admin Kanban");
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ context("Kanban Board", () => {
|
|||
cy.get(".kanban .column-options").should("have.length", 0);
|
||||
|
||||
cy.switch_to_user("Administrator");
|
||||
cy.call("frappe.client.delete", { doctype: "User", name: noSystemManager });
|
||||
cy.call("frappe.client.delete", { doctype: "User", name: not_system_manager });
|
||||
});
|
||||
|
||||
after(() => {
|
||||
|
|
|
|||
|
|
@ -13,15 +13,8 @@ context("List View", () => {
|
|||
it("Keep checkbox checked after Refresh", { scrollBehavior: false }, () => {
|
||||
cy.go_to_list("ToDo");
|
||||
cy.clear_filters();
|
||||
cy.get(".list-row-container .list-row-checkbox").click({
|
||||
multiple: true,
|
||||
force: true,
|
||||
});
|
||||
cy.get(".actions-btn-group button").contains("Actions").should("be.visible");
|
||||
cy.intercept("/api/method/frappe.desk.reportview.get").as("list-refresh");
|
||||
cy.wait(3000); // wait before you hit another refresh
|
||||
cy.get('button[data-original-title="Refresh"]').click();
|
||||
cy.wait("@list-refresh");
|
||||
cy.get(".list-header-subject > .list-subject > .list-check-all").click();
|
||||
cy.get("button[data-original-title='Refresh']").click();
|
||||
cy.get(".list-row-container .list-row-checkbox:checked").should("be.visible");
|
||||
});
|
||||
|
||||
|
|
@ -39,11 +32,8 @@ context("List View", () => {
|
|||
];
|
||||
cy.go_to_list("ToDo");
|
||||
cy.clear_filters();
|
||||
cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({
|
||||
multiple: true,
|
||||
force: true,
|
||||
});
|
||||
cy.get(".actions-btn-group button").contains("Actions").should("be.visible").click();
|
||||
cy.get(".list-header-subject > .list-subject > .list-check-all").click();
|
||||
cy.findByRole("button", { name: "Actions" }).click();
|
||||
cy.get(".dropdown-menu li:visible .dropdown-item")
|
||||
.should("have.length", 9)
|
||||
.each((el, index) => {
|
||||
|
|
@ -56,8 +46,7 @@ context("List View", () => {
|
|||
}).as("bulk-approval");
|
||||
cy.wrap(elements).contains("Approve").click();
|
||||
cy.wait("@bulk-approval");
|
||||
cy.wait(300);
|
||||
cy.get_open_dialog().find(".btn-modal-close").click();
|
||||
cy.hide_dialog();
|
||||
cy.reload();
|
||||
cy.clear_filters();
|
||||
cy.get(".list-row-container:visible").should("contain", "Approved");
|
||||
|
|
|
|||
|
|
@ -1,21 +1,26 @@
|
|||
context("Navigation", () => {
|
||||
before(() => {
|
||||
cy.visit("/login");
|
||||
cy.login();
|
||||
cy.visit("/app/website");
|
||||
});
|
||||
it("Navigate to route with hash in document name", () => {
|
||||
cy.insert_doc("ToDo", {
|
||||
__newname: "ABC#123",
|
||||
description: "Test this",
|
||||
ignore_duplicate: true,
|
||||
});
|
||||
cy.visit("/app/todo/ABC#123");
|
||||
cy.insert_doc(
|
||||
"ToDo",
|
||||
{
|
||||
__newname: "ABC#123",
|
||||
description: "Test this",
|
||||
},
|
||||
true
|
||||
);
|
||||
cy.visit(`/app/todo/${encodeURIComponent("ABC#123")}`);
|
||||
cy.title().should("eq", "Test this - ABC#123");
|
||||
cy.get_field("description", "Text Editor").contains("Test this");
|
||||
cy.go("back");
|
||||
cy.title().should("eq", "Website");
|
||||
});
|
||||
|
||||
it.only("Navigate to previous page after login", () => {
|
||||
it("Navigate to previous page after login", () => {
|
||||
cy.visit("/app/todo");
|
||||
cy.get(".page-head").findByTitle("To Do").should("be.visible");
|
||||
cy.clear_filters();
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Cypress.Commands.add("login", (email, password) => {
|
|||
if (!password) {
|
||||
password = Cypress.env("adminPassword");
|
||||
}
|
||||
cy.request({
|
||||
return cy.request({
|
||||
url: "/api/method/login",
|
||||
method: "POST",
|
||||
body: {
|
||||
|
|
@ -373,7 +373,9 @@ Cypress.Commands.add("update_doc", (doctype, docname, args) => {
|
|||
|
||||
Cypress.Commands.add("switch_to_user", (user) => {
|
||||
cy.call("logout");
|
||||
cy.wait(200);
|
||||
cy.login(user);
|
||||
cy.reload();
|
||||
});
|
||||
|
||||
Cypress.Commands.add("add_role", (user, role) => {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ const argv = yargs
|
|||
type: "boolean",
|
||||
description: "Run build command for apps",
|
||||
})
|
||||
.option("save-metafiles", {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Saves esbuild metafiles for built assets. Useful for analyzing bundle size. More info: https://esbuild.github.io/api/#metafile",
|
||||
})
|
||||
.example("node esbuild --apps frappe,erpnext", "Run build only for frappe and erpnext")
|
||||
.example(
|
||||
"node esbuild --files frappe/website.bundle.js,frappe/desk.bundle.js",
|
||||
|
|
@ -89,7 +94,7 @@ execute()
|
|||
.then(() => RUN_BUILD_COMMAND && run_build_command_for_apps(APPS))
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
if (WATCH_MODE) {
|
||||
|
|
@ -401,6 +406,13 @@ async function write_assets_json(metafile) {
|
|||
|
||||
await fs.promises.writeFile(assets_json_path, JSON.stringify(new_assets_json, null, 4));
|
||||
await update_assets_json_in_cache();
|
||||
if (argv["save-metafiles"]) {
|
||||
// use current timestamp in readable formate as a suffix for filename
|
||||
let current_timestamp = new Date().getTime();
|
||||
const metafile_name = `meta-${current_timestamp}.json`;
|
||||
await fs.promises.writeFile(`${metafile_name}`, JSON.stringify(metafile));
|
||||
log(`Saved metafile as ${metafile_name}`);
|
||||
}
|
||||
return {
|
||||
new_assets_json,
|
||||
prev_assets_json,
|
||||
|
|
@ -446,9 +458,9 @@ function run_build_command_for_apps(apps) {
|
|||
|
||||
async function notify_redis({ error, success, changed_files }) {
|
||||
// notify redis which in turns tells socketio to publish this to browser
|
||||
let subscriber = get_redis_subscriber("redis_socketio");
|
||||
let subscriber = get_redis_subscriber("redis_queue");
|
||||
subscriber.on("error", (_) => {
|
||||
log_warn("Cannot connect to redis_socketio for browser events");
|
||||
log_warn("Cannot connect to redis_queue for browser events");
|
||||
});
|
||||
|
||||
let payload = null;
|
||||
|
|
@ -482,9 +494,9 @@ async function notify_redis({ error, success, changed_files }) {
|
|||
}
|
||||
|
||||
function open_in_editor() {
|
||||
let subscriber = get_redis_subscriber("redis_socketio");
|
||||
let subscriber = get_redis_subscriber("redis_queue");
|
||||
subscriber.on("error", (_) => {
|
||||
log_warn("Cannot connect to redis_socketio for open_in_editor events");
|
||||
log_warn("Cannot connect to redis_queue for open_in_editor events");
|
||||
});
|
||||
subscriber.on("message", (event, file) => {
|
||||
if (event === "open_in_editor") {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ be used to build database driven apps.
|
|||
Read the documentation: https://frappeframework.com/docs
|
||||
"""
|
||||
import functools
|
||||
import gc
|
||||
import importlib
|
||||
import inspect
|
||||
import json
|
||||
|
|
@ -57,6 +58,7 @@ re._MAXCACHE = (
|
|||
50 # reduced from default 512 given we are already maintaining this on parent worker
|
||||
)
|
||||
|
||||
_tune_gc = bool(os.environ.get("FRAPPE_TUNE_GC", False))
|
||||
|
||||
if _dev_server:
|
||||
warnings.simplefilter("always", DeprecationWarning)
|
||||
|
|
@ -380,7 +382,7 @@ def errprint(msg: str) -> None:
|
|||
|
||||
|
||||
def print_sql(enable: bool = True) -> None:
|
||||
return cache().set_value("flag_print_sql", enable)
|
||||
return cache.set_value("flag_print_sql", enable)
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
|
|
@ -925,7 +927,6 @@ def has_permission(
|
|||
ptype="read",
|
||||
doc=None,
|
||||
user=None,
|
||||
verbose=False,
|
||||
throw=False,
|
||||
*,
|
||||
parent_doctype=None,
|
||||
|
|
@ -938,7 +939,6 @@ def has_permission(
|
|||
:param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`.
|
||||
:param doc: [optional] Checks User permissions for given doc.
|
||||
:param user: [optional] Check for given user. Default: current user.
|
||||
:param verbose: DEPRECATED, will be removed in a future release.
|
||||
:param parent_doctype: Required when checking permission for a child DocType (unless doc is specified).
|
||||
"""
|
||||
import frappe.permissions
|
||||
|
|
@ -1016,7 +1016,7 @@ def is_table(doctype: str) -> bool:
|
|||
def get_tables():
|
||||
return db.get_values("DocType", filters={"istable": 1}, order_by=None, pluck=True)
|
||||
|
||||
tables = cache().get_value("is_table", get_tables)
|
||||
tables = cache.get_value("is_table", get_tables)
|
||||
return doctype in tables
|
||||
|
||||
|
||||
|
|
@ -1043,7 +1043,7 @@ def generate_hash(txt: str | None = None, length: int = 56) -> str:
|
|||
def reset_metadata_version():
|
||||
"""Reset `metadata_version` (Client (Javascript) build ID) hash."""
|
||||
v = generate_hash()
|
||||
cache().set_value("metadata_version", v)
|
||||
cache.set_value("metadata_version", v)
|
||||
return v
|
||||
|
||||
|
||||
|
|
@ -1079,7 +1079,7 @@ def set_value(doctype, docname, fieldname, value=None):
|
|||
|
||||
|
||||
def get_cached_doc(*args, **kwargs) -> "Document":
|
||||
if (key := can_cache_doc(args)) and (doc := cache().get_value(key)):
|
||||
if (key := can_cache_doc(args)) and (doc := cache.get_value(key)):
|
||||
return doc
|
||||
|
||||
# Not found in cache, fetch from DB
|
||||
|
|
@ -1095,7 +1095,7 @@ def get_cached_doc(*args, **kwargs) -> "Document":
|
|||
|
||||
|
||||
def _set_document_in_cache(key: str, doc: "Document") -> None:
|
||||
cache().set_value(key, doc)
|
||||
cache.set_value(key, doc)
|
||||
|
||||
|
||||
def can_cache_doc(args) -> str | None:
|
||||
|
|
@ -1122,9 +1122,9 @@ def get_document_cache_key(doctype: str, name: str):
|
|||
def clear_document_cache(doctype: str, name: str | None = None) -> None:
|
||||
def clear_in_redis():
|
||||
if name is not None:
|
||||
cache().delete_value(get_document_cache_key(doctype, name))
|
||||
cache.delete_value(get_document_cache_key(doctype, name))
|
||||
else:
|
||||
cache().delete_keys(get_document_cache_key(doctype, ""))
|
||||
cache.delete_keys(get_document_cache_key(doctype, ""))
|
||||
|
||||
clear_in_redis()
|
||||
if hasattr(db, "after_commit"):
|
||||
|
|
@ -1214,7 +1214,7 @@ def get_doc(*args, **kwargs):
|
|||
doc = frappe.model.document.get_doc(*args, **kwargs)
|
||||
|
||||
# Replace cache if stale one exists
|
||||
if (key := can_cache_doc(args)) and cache().exists(key):
|
||||
if (key := can_cache_doc(args)) and cache.exists(key):
|
||||
_set_document_in_cache(key, doc)
|
||||
|
||||
return doc
|
||||
|
|
@ -1428,7 +1428,7 @@ def get_all_apps(with_internal_apps=True, sites_path=None):
|
|||
|
||||
|
||||
@request_cache
|
||||
def get_installed_apps(sort=False, frappe_last=False, *, _ensure_on_bench=False):
|
||||
def get_installed_apps(*, _ensure_on_bench=False):
|
||||
"""
|
||||
Get list of installed apps in current site.
|
||||
|
||||
|
|
@ -1436,8 +1436,6 @@ def get_installed_apps(sort=False, frappe_last=False, *, _ensure_on_bench=False)
|
|||
:param frappe_last: [DEPRECATED] Keep frappe last. Do not use this, reverse the app list instead.
|
||||
:param ensure_on_bench: Only return apps that are present on bench.
|
||||
"""
|
||||
from frappe.utils.deprecations import deprecation_warning
|
||||
|
||||
if getattr(flags, "in_install_db", True):
|
||||
return []
|
||||
|
||||
|
|
@ -1446,23 +1444,10 @@ def get_installed_apps(sort=False, frappe_last=False, *, _ensure_on_bench=False)
|
|||
|
||||
installed = json.loads(db.get_global("installed_apps") or "[]")
|
||||
|
||||
if sort:
|
||||
if not local.all_apps:
|
||||
local.all_apps = cache().get_value("all_apps", get_all_apps)
|
||||
|
||||
deprecation_warning("`sort` argument is deprecated and will be removed in v15.")
|
||||
installed = [app for app in local.all_apps if app in installed]
|
||||
|
||||
if _ensure_on_bench:
|
||||
all_apps = cache().get_value("all_apps", get_all_apps)
|
||||
all_apps = cache.get_value("all_apps", get_all_apps)
|
||||
installed = [app for app in installed if app in all_apps]
|
||||
|
||||
if frappe_last:
|
||||
deprecation_warning("`frappe_last` argument is deprecated and will be removed in v15.")
|
||||
if "frappe" in installed:
|
||||
installed.remove("frappe")
|
||||
installed.append("frappe")
|
||||
|
||||
return installed
|
||||
|
||||
|
||||
|
|
@ -1525,7 +1510,7 @@ def get_hooks(
|
|||
if conf.developer_mode:
|
||||
hooks = _dict(_load_app_hooks())
|
||||
else:
|
||||
hooks = _dict(cache().get_value("app_hooks", _load_app_hooks))
|
||||
hooks = _dict(cache.get_value("app_hooks", _load_app_hooks))
|
||||
|
||||
if hook:
|
||||
return hooks.get(hook, ([] if default == "_KEEP_DEFAULT_LIST" else default))
|
||||
|
|
@ -1555,11 +1540,9 @@ def append_hook(target, key, value):
|
|||
|
||||
def setup_module_map():
|
||||
"""Rebuild map of all modules (internal)."""
|
||||
_cache = cache()
|
||||
|
||||
if conf.db_name:
|
||||
local.app_modules = _cache.get_value("app_modules")
|
||||
local.module_app = _cache.get_value("module_app")
|
||||
local.app_modules = cache.get_value("app_modules")
|
||||
local.module_app = cache.get_value("module_app")
|
||||
|
||||
if not (local.app_modules and local.module_app):
|
||||
local.module_app, local.app_modules = {}, {}
|
||||
|
|
@ -1571,8 +1554,8 @@ def setup_module_map():
|
|||
local.app_modules[app].append(module)
|
||||
|
||||
if conf.db_name:
|
||||
_cache.set_value("app_modules", local.app_modules)
|
||||
_cache.set_value("module_app", local.module_app)
|
||||
cache.set_value("app_modules", local.app_modules)
|
||||
cache.set_value("module_app", local.module_app)
|
||||
|
||||
|
||||
def get_file_items(path, raise_not_found=False, ignore_empty_lines=True):
|
||||
|
|
@ -1861,7 +1844,7 @@ def redirect_to_message(title, html, http_status_code=None, context=None, indica
|
|||
if indicator_color:
|
||||
message["context"].update({"indicator_color": indicator_color})
|
||||
|
||||
cache().set_value(f"message_id:{message_id}", message, expires_in_sec=60)
|
||||
cache.set_value(f"message_id:{message_id}", message, expires_in_sec=60)
|
||||
location = f"/message?id={message_id}"
|
||||
|
||||
if not getattr(local, "is_ajax", False):
|
||||
|
|
@ -2437,4 +2420,30 @@ def mock(type, size=1, locale="en"):
|
|||
return squashify(results)
|
||||
|
||||
|
||||
from frappe.desk.search import validate_and_sanitize_search_inputs # noqa
|
||||
def validate_and_sanitize_search_inputs(fn):
|
||||
@functools.wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
from frappe.desk.search import sanitize_searchfield
|
||||
from frappe.utils import cint
|
||||
|
||||
kwargs.update(dict(zip(fn.__code__.co_varnames, args)))
|
||||
sanitize_searchfield(kwargs["searchfield"])
|
||||
kwargs["start"] = cint(kwargs["start"])
|
||||
kwargs["page_len"] = cint(kwargs["page_len"])
|
||||
|
||||
if kwargs["doctype"] and not db.exists("DocType", kwargs["doctype"]):
|
||||
return []
|
||||
|
||||
return fn(**kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
if _tune_gc:
|
||||
# generational GC gets triggered after certain allocs (g0) which is 700 by default.
|
||||
# This number is quite small for frappe where a single query can potentially create 700+
|
||||
# objects easily.
|
||||
# Bump this number higher, this will make GC less aggressive but that improves performance of
|
||||
# everything else.
|
||||
g0, g1, g2 = gc.get_threshold() # defaults are 700, 10, 10.
|
||||
gc.set_threshold(g0 * 10, g1 * 2, g2 * 2)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
import gc
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
|
@ -30,6 +31,30 @@ _site = None
|
|||
_sites_path = os.environ.get("SITES_PATH", ".")
|
||||
|
||||
|
||||
# If gc.freeze is done then importing modules before forking allows us to share the memory
|
||||
if frappe._tune_gc:
|
||||
import frappe.boot
|
||||
import frappe.client
|
||||
import frappe.core.doctype.user.user
|
||||
import frappe.database.mariadb.database # Load database related utils
|
||||
import frappe.database.query
|
||||
import frappe.desk.desktop # workspace
|
||||
import frappe.model.db_query
|
||||
import frappe.query_builder
|
||||
import frappe.utils.background_jobs # Enqueue is very common
|
||||
import frappe.utils.data # common utils
|
||||
import frappe.utils.jinja # web page rendering
|
||||
import frappe.utils.jinja_globals
|
||||
import frappe.utils.redis_wrapper # Exact redis_wrapper
|
||||
import frappe.utils.safe_exec
|
||||
import frappe.utils.typing_validations # any whitelisted method uses this
|
||||
import frappe.website.path_resolver # all the page types and resolver
|
||||
import frappe.website.router # Website router
|
||||
import frappe.website.website_generator # web page doctypes
|
||||
|
||||
# end: module pre-loading
|
||||
|
||||
|
||||
@local_manager.middleware
|
||||
@Request.application
|
||||
def application(request: Request):
|
||||
|
|
@ -157,6 +182,8 @@ def log_request(request, response):
|
|||
{
|
||||
"site": get_site_name(request.host),
|
||||
"remote_addr": getattr(request, "remote_addr", "NOTFOUND"),
|
||||
"pid": os.getpid(),
|
||||
"user": getattr(frappe.local.session, "user", "NOTFOUND"),
|
||||
"base_url": getattr(request, "base_url", "NOTFOUND"),
|
||||
"full_path": getattr(request, "full_path", "NOTFOUND"),
|
||||
"method": getattr(request, "method", "NOTFOUND"),
|
||||
|
|
@ -392,3 +419,17 @@ def serve(
|
|||
use_evalex=not in_test_env,
|
||||
threaded=not no_threading,
|
||||
)
|
||||
|
||||
|
||||
# Both Gunicorn and RQ use forking to spawn workers. In an ideal world, the fork should be sharing
|
||||
# most of the memory if there are no writes made to data because of Copy on Write, however,
|
||||
# python's GC is not CoW friendly and writes to data even if user-code doesn't. Specifically, the
|
||||
# generational GC which stores and mutates every python object: `PyGC_Head`
|
||||
#
|
||||
# Calling gc.freeze() moves all the objects imported so far into permanant generation and hence
|
||||
# doesn't mutate `PyGC_Head`
|
||||
#
|
||||
# Refer to issue for more info: https://github.com/frappe/frappe/issues/18927
|
||||
if frappe._tune_gc:
|
||||
gc.collect() # clean up any garbage created so far before freeze
|
||||
gc.freeze()
|
||||
|
|
|
|||
|
|
@ -188,10 +188,10 @@ class LoginManager:
|
|||
frappe.response["full_name"] = self.full_name
|
||||
|
||||
# redirect information
|
||||
redirect_to = frappe.cache().hget("redirect_after_login", self.user)
|
||||
redirect_to = frappe.cache.hget("redirect_after_login", self.user)
|
||||
if redirect_to:
|
||||
frappe.local.response["redirect_to"] = redirect_to
|
||||
frappe.cache().hdel("redirect_after_login", self.user)
|
||||
frappe.cache.hdel("redirect_after_login", self.user)
|
||||
|
||||
frappe.local.cookie_manager.set_cookie("full_name", self.full_name)
|
||||
frappe.local.cookie_manager.set_cookie("user_id", self.user)
|
||||
|
|
@ -482,15 +482,15 @@ class LoginAttemptTracker:
|
|||
|
||||
@property
|
||||
def login_failed_count(self):
|
||||
return frappe.cache().hget("login_failed_count", self.user_name)
|
||||
return frappe.cache.hget("login_failed_count", self.user_name)
|
||||
|
||||
@login_failed_count.setter
|
||||
def login_failed_count(self, count):
|
||||
frappe.cache().hset("login_failed_count", self.user_name, count)
|
||||
frappe.cache.hset("login_failed_count", self.user_name, count)
|
||||
|
||||
@login_failed_count.deleter
|
||||
def login_failed_count(self):
|
||||
frappe.cache().hdel("login_failed_count", self.user_name)
|
||||
frappe.cache.hdel("login_failed_count", self.user_name)
|
||||
|
||||
@property
|
||||
def login_failed_time(self):
|
||||
|
|
@ -498,15 +498,15 @@ class LoginAttemptTracker:
|
|||
|
||||
For every user we track only First failed login attempt time within lock interval of time.
|
||||
"""
|
||||
return frappe.cache().hget("login_failed_time", self.user_name)
|
||||
return frappe.cache.hget("login_failed_time", self.user_name)
|
||||
|
||||
@login_failed_time.setter
|
||||
def login_failed_time(self, timestamp):
|
||||
frappe.cache().hset("login_failed_time", self.user_name, timestamp)
|
||||
frappe.cache.hset("login_failed_time", self.user_name, timestamp)
|
||||
|
||||
@login_failed_time.deleter
|
||||
def login_failed_time(self):
|
||||
frappe.cache().hdel("login_failed_time", self.user_name)
|
||||
frappe.cache.hdel("login_failed_time", self.user_name)
|
||||
|
||||
def add_failure_attempt(self):
|
||||
"""Log user failure attempts into the system.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class TestMilestoneTracker(FrappeTestCase):
|
|||
def test_milestone(self):
|
||||
frappe.db.delete("Milestone Tracker")
|
||||
|
||||
frappe.cache().delete_key("milestone_tracker_map")
|
||||
frappe.cache.delete_key("milestone_tracker_map")
|
||||
|
||||
milestone_tracker = frappe.get_doc(
|
||||
dict(doctype="Milestone Tracker", document_type="ToDo", track_field="status")
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_p
|
|||
from frappe.social.doctype.energy_point_settings.energy_point_settings import (
|
||||
is_energy_point_enabled,
|
||||
)
|
||||
from frappe.translate import get_lang_dict, get_messages_for_boot, get_translated_doctypes
|
||||
from frappe.utils import add_user_info, cstr, get_system_timezone
|
||||
from frappe.utils.change_log import get_versions
|
||||
from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled
|
||||
|
|
@ -29,6 +28,8 @@ from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabl
|
|||
|
||||
def get_bootinfo():
|
||||
"""build and return boot info"""
|
||||
from frappe.translate import get_lang_dict, get_translated_doctypes
|
||||
|
||||
frappe.set_user_lang(frappe.session.user)
|
||||
bootinfo = frappe._dict()
|
||||
hooks = frappe.get_hooks()
|
||||
|
|
@ -149,10 +150,8 @@ def get_allowed_report_names(cache=False) -> set[str]:
|
|||
|
||||
|
||||
def get_user_pages_or_reports(parent, cache=False):
|
||||
_cache = frappe.cache()
|
||||
|
||||
if cache:
|
||||
has_role = _cache.get_value("has_role:" + parent, user=frappe.session.user)
|
||||
has_role = frappe.cache.get_value("has_role:" + parent, user=frappe.session.user)
|
||||
if has_role:
|
||||
return has_role
|
||||
|
||||
|
|
@ -254,11 +253,13 @@ def get_user_pages_or_reports(parent, cache=False):
|
|||
has_role.pop(r, None)
|
||||
|
||||
# Expire every six hours
|
||||
_cache.set_value("has_role:" + parent, has_role, frappe.session.user, 21600)
|
||||
frappe.cache.set_value("has_role:" + parent, has_role, frappe.session.user, 21600)
|
||||
return has_role
|
||||
|
||||
|
||||
def load_translations(bootinfo):
|
||||
from frappe.translate import get_messages_for_boot
|
||||
|
||||
bootinfo["lang"] = frappe.lang
|
||||
bootinfo["__messages"] = get_messages_for_boot()
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ from tempfile import mkdtemp, mktemp
|
|||
from urllib.parse import urlparse
|
||||
|
||||
import click
|
||||
import psutil
|
||||
from requests import head
|
||||
from requests.exceptions import HTTPError
|
||||
from semantic_version import Version
|
||||
|
||||
import frappe
|
||||
|
|
@ -27,7 +24,7 @@ class AssetsNotDownloadedError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class AssetsDontExistError(HTTPError):
|
||||
class AssetsDontExistError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -78,6 +75,8 @@ def build_missing_files():
|
|||
|
||||
|
||||
def get_assets_link(frappe_head) -> str:
|
||||
import requests
|
||||
|
||||
tag = getoutput(
|
||||
r"cd ../apps/frappe && git show-ref --tags -d | grep %s | sed -e 's,.*"
|
||||
r" refs/tags/,,' -e 's/\^{}//'" % frappe_head
|
||||
|
|
@ -89,7 +88,7 @@ def get_assets_link(frappe_head) -> str:
|
|||
else:
|
||||
url = f"http://assets.frappeframework.com/{frappe_head}.tar.gz"
|
||||
|
||||
if not head(url):
|
||||
if not requests.head(url):
|
||||
reference = f"Release {tag}" if tag else f"Commit {frappe_head}"
|
||||
raise AssetsDontExistError(f"Assets for {reference} don't exist")
|
||||
|
||||
|
|
@ -227,11 +226,10 @@ def bundle(
|
|||
mode,
|
||||
apps=None,
|
||||
hard_link=False,
|
||||
make_copy=False,
|
||||
restore=False,
|
||||
verbose=False,
|
||||
skip_frappe=False,
|
||||
files=None,
|
||||
save_metafiles=False,
|
||||
):
|
||||
"""concat / minify js files"""
|
||||
setup()
|
||||
|
|
@ -251,6 +249,9 @@ def bundle(
|
|||
|
||||
command += " --run-build-command"
|
||||
|
||||
if save_metafiles:
|
||||
command += " --save-metafiles"
|
||||
|
||||
check_node_executable()
|
||||
frappe_app_path = frappe.get_app_path("frappe", "..")
|
||||
frappe.commands.popen(command, cwd=frappe_app_path, env=get_node_env(), raise_err=True)
|
||||
|
|
@ -277,8 +278,8 @@ def watch(apps=None):
|
|||
def check_node_executable():
|
||||
node_version = Version(subprocess.getoutput("node -v")[1:])
|
||||
warn = "⚠️ "
|
||||
if node_version.major < 14:
|
||||
click.echo(f"{warn} Please update your node version to 14")
|
||||
if node_version.major < 18:
|
||||
click.echo(f"{warn} Please update your node version to 18")
|
||||
if not shutil.which("yarn"):
|
||||
click.echo(f"{warn} Please install yarn using below command and try again.\nnpm install -g yarn")
|
||||
click.echo()
|
||||
|
|
@ -290,6 +291,8 @@ def get_node_env():
|
|||
|
||||
|
||||
def get_safe_max_old_space_size():
|
||||
import psutil
|
||||
|
||||
safe_max_old_space_size = 0
|
||||
try:
|
||||
total_memory = psutil.virtual_memory().total / (1024 * 1024)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.desk.notifications import clear_notifications, delete_notification_count_for
|
||||
|
||||
common_default_keys = ["__default", "__global"]
|
||||
|
||||
|
|
@ -79,7 +76,7 @@ doctype_cache_keys = (
|
|||
|
||||
|
||||
def clear_user_cache(user=None):
|
||||
cache = frappe.cache()
|
||||
from frappe.desk.notifications import clear_notifications
|
||||
|
||||
# this will automatically reload the global cache
|
||||
# so it is important to clear this first
|
||||
|
|
@ -87,20 +84,19 @@ def clear_user_cache(user=None):
|
|||
|
||||
if user:
|
||||
for name in user_cache_keys:
|
||||
cache.hdel(name, user)
|
||||
cache.delete_keys("user:" + user)
|
||||
frappe.cache.hdel(name, user)
|
||||
frappe.cache.delete_keys("user:" + user)
|
||||
clear_defaults_cache(user)
|
||||
else:
|
||||
for name in user_cache_keys:
|
||||
cache.delete_key(name)
|
||||
frappe.cache.delete_key(name)
|
||||
clear_defaults_cache()
|
||||
clear_global_cache()
|
||||
|
||||
|
||||
def clear_domain_cache(user=None):
|
||||
cache = frappe.cache()
|
||||
domain_cache_keys = ("domain_restricted_doctypes", "domain_restricted_pages")
|
||||
cache.delete_value(domain_cache_keys)
|
||||
frappe.cache.delete_value(domain_cache_keys)
|
||||
|
||||
|
||||
def clear_global_cache():
|
||||
|
|
@ -108,17 +104,17 @@ def clear_global_cache():
|
|||
|
||||
clear_doctype_cache()
|
||||
clear_website_cache()
|
||||
frappe.cache().delete_value(global_cache_keys)
|
||||
frappe.cache().delete_value(bench_cache_keys)
|
||||
frappe.cache.delete_value(global_cache_keys)
|
||||
frappe.cache.delete_value(bench_cache_keys)
|
||||
frappe.setup_module_map()
|
||||
|
||||
|
||||
def clear_defaults_cache(user=None):
|
||||
if user:
|
||||
for p in [user] + common_default_keys:
|
||||
frappe.cache().hdel("defaults", p)
|
||||
frappe.cache.hdel("defaults", p)
|
||||
elif frappe.flags.in_install != "frappe":
|
||||
frappe.cache().delete_key("defaults")
|
||||
frappe.cache.delete_key("defaults")
|
||||
|
||||
|
||||
def clear_doctype_cache(doctype=None):
|
||||
|
|
@ -131,15 +127,15 @@ def clear_doctype_cache(doctype=None):
|
|||
|
||||
|
||||
def _clear_doctype_cache_form_redis(doctype: str | None = None):
|
||||
cache = frappe.cache()
|
||||
from frappe.desk.notifications import delete_notification_count_for
|
||||
|
||||
for key in ("is_table", "doctype_modules"):
|
||||
cache.delete_value(key)
|
||||
frappe.cache.delete_value(key)
|
||||
|
||||
def clear_single(dt):
|
||||
frappe.clear_document_cache(dt)
|
||||
for name in doctype_cache_keys:
|
||||
cache.hdel(name, dt)
|
||||
frappe.cache.hdel(name, dt)
|
||||
|
||||
if doctype:
|
||||
clear_single(doctype)
|
||||
|
|
@ -163,8 +159,8 @@ def _clear_doctype_cache_form_redis(doctype: str | None = None):
|
|||
else:
|
||||
# clear all
|
||||
for name in doctype_cache_keys:
|
||||
cache.delete_value(name)
|
||||
cache.delete_keys("document_cache::")
|
||||
frappe.cache.delete_value(name)
|
||||
frappe.cache.delete_keys("document_cache::")
|
||||
|
||||
|
||||
def clear_controller_cache(doctype=None):
|
||||
|
|
@ -177,7 +173,7 @@ def clear_controller_cache(doctype=None):
|
|||
|
||||
|
||||
def get_doctype_map(doctype, name, filters=None, order_by=None):
|
||||
return frappe.cache().hget(
|
||||
return frappe.cache.hget(
|
||||
get_doctype_map_key(doctype),
|
||||
name,
|
||||
lambda: frappe.get_all(doctype, filters=filters, order_by=order_by, ignore_ddl=True),
|
||||
|
|
@ -185,7 +181,7 @@ def get_doctype_map(doctype, name, filters=None, order_by=None):
|
|||
|
||||
|
||||
def clear_doctype_map(doctype, name):
|
||||
frappe.cache().hdel(frappe.scrub(doctype) + "_map", name)
|
||||
frappe.cache.hdel(frappe.scrub(doctype) + "_map", name)
|
||||
|
||||
|
||||
def build_table_count_cache():
|
||||
|
|
@ -198,7 +194,6 @@ def build_table_count_cache():
|
|||
):
|
||||
return
|
||||
|
||||
_cache = frappe.cache()
|
||||
table_name = frappe.qb.Field("table_name").as_("name")
|
||||
table_rows = frappe.qb.Field("table_rows").as_("count")
|
||||
information_schema = frappe.qb.Schema("information_schema")
|
||||
|
|
@ -207,7 +202,7 @@ def build_table_count_cache():
|
|||
as_dict=True
|
||||
)
|
||||
counts = {d.get("name").replace("tab", "", 1): d.get("count", None) for d in data}
|
||||
_cache.set_value("information_schema:counts", counts)
|
||||
frappe.cache.set_value("information_schema:counts", counts)
|
||||
|
||||
return counts
|
||||
|
||||
|
|
@ -221,11 +216,10 @@ def build_domain_restriced_doctype_cache(*args, **kwargs):
|
|||
or frappe.flags.in_setup_wizard
|
||||
):
|
||||
return
|
||||
_cache = frappe.cache()
|
||||
active_domains = frappe.get_active_domains()
|
||||
doctypes = frappe.get_all("DocType", filters={"restrict_to_domain": ("IN", active_domains)})
|
||||
doctypes = [doc.name for doc in doctypes]
|
||||
_cache.set_value("domain_restricted_doctypes", doctypes)
|
||||
frappe.cache.set_value("domain_restricted_doctypes", doctypes)
|
||||
|
||||
return doctypes
|
||||
|
||||
|
|
@ -239,10 +233,9 @@ def build_domain_restriced_page_cache(*args, **kwargs):
|
|||
or frappe.flags.in_setup_wizard
|
||||
):
|
||||
return
|
||||
_cache = frappe.cache()
|
||||
active_domains = frappe.get_active_domains()
|
||||
pages = frappe.get_all("Page", filters={"restrict_to_domain": ("IN", active_domains)})
|
||||
pages = [page.name for page in pages]
|
||||
_cache.set_value("domain_restricted_pages", pages)
|
||||
frappe.cache.set_value("domain_restricted_pages", pages)
|
||||
|
||||
return pages
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import os
|
|||
import click
|
||||
|
||||
import frappe
|
||||
from frappe.installer import update_site_config
|
||||
from frappe.utils.redis_queue import RedisQueue
|
||||
|
||||
|
||||
|
|
@ -23,6 +22,8 @@ def create_rq_users(set_admin_password=False, use_rq_auth=False):
|
|||
acl config file will be used by redis server while starting the server
|
||||
and app config is used by app while connecting to redis server.
|
||||
"""
|
||||
from frappe.installer import update_site_config
|
||||
|
||||
acl_file_path = os.path.abspath("../config/redis_queue.acl")
|
||||
|
||||
with frappe.init_site():
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import click
|
|||
# imports - module imports
|
||||
import frappe
|
||||
from frappe.commands import get_site, pass_context
|
||||
from frappe.core.doctype.log_settings.log_settings import LOG_DOCTYPES
|
||||
from frappe.exceptions import SiteNotSpecifiedError
|
||||
|
||||
|
||||
|
|
@ -1199,11 +1198,12 @@ def build_search_index(context):
|
|||
|
||||
|
||||
@click.command("clear-log-table")
|
||||
@click.option("--doctype", required=True, type=click.Choice(LOG_DOCTYPES), help="Log DocType")
|
||||
@click.option("--doctype", required=True, type=str, help="Log DocType")
|
||||
@click.option("--days", type=int, help="Keep records for days")
|
||||
@click.option("--no-backup", is_flag=True, default=False, help="Do not backup the table")
|
||||
@pass_context
|
||||
def clear_log_table(context, doctype, days, no_backup):
|
||||
|
||||
"""If any logtype table grows too large then clearing it with DELETE query
|
||||
is not feasible in reasonable time. This command copies recent data to new
|
||||
table and replaces current table with new smaller table.
|
||||
|
|
@ -1211,6 +1211,7 @@ def clear_log_table(context, doctype, days, no_backup):
|
|||
|
||||
ref: https://mariadb.com/kb/en/big-deletes/#deleting-more-than-half-a-table
|
||||
"""
|
||||
from frappe.core.doctype.log_settings.log_settings import LOG_DOCTYPES
|
||||
from frappe.core.doctype.log_settings.log_settings import clear_log_table as clear_logs
|
||||
from frappe.utils.backups import scheduled_backup
|
||||
|
||||
|
|
|
|||
|
|
@ -102,10 +102,28 @@ def import_translations(context, lang, path):
|
|||
frappe.destroy()
|
||||
|
||||
|
||||
@click.command("migrate-translations")
|
||||
@click.argument("source-app")
|
||||
@click.argument("target-app")
|
||||
@pass_context
|
||||
def migrate_translations(context, source_app, target_app):
|
||||
"Migrate target-app-specific translations from source-app to target-app"
|
||||
import frappe.translate
|
||||
|
||||
site = get_site(context)
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.translate.migrate_translations(source_app, target_app)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
commands = [
|
||||
build_message_files,
|
||||
get_untranslated,
|
||||
import_translations,
|
||||
new_language,
|
||||
update_translations,
|
||||
migrate_translations,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -13,10 +13,6 @@ from frappe.exceptions import SiteNotSpecifiedError
|
|||
from frappe.utils import cint, update_progress_bar
|
||||
|
||||
find_executable = which # backwards compatibility
|
||||
DATA_IMPORT_DEPRECATION = (
|
||||
"[DEPRECATED] The `import-csv` command used 'Data Import Legacy' which has been deprecated.\n"
|
||||
"Use `data-import` command instead to import data via 'Data Import'."
|
||||
)
|
||||
EXTRA_ARGS_CTX = {"ignore_unknown_options": True, "allow_extra_args": True}
|
||||
|
||||
|
||||
|
|
@ -30,32 +26,25 @@ EXTRA_ARGS_CTX = {"ignore_unknown_options": True, "allow_extra_args": True}
|
|||
help="Copy the files instead of symlinking",
|
||||
envvar="FRAPPE_HARD_LINK_ASSETS",
|
||||
)
|
||||
@click.option(
|
||||
"--make-copy",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="[DEPRECATED] Copy the files instead of symlinking",
|
||||
)
|
||||
@click.option(
|
||||
"--restore",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="[DEPRECATED] Copy the files instead of symlinking with force",
|
||||
)
|
||||
@click.option("--production", is_flag=True, default=False, help="Build assets in production mode")
|
||||
@click.option("--verbose", is_flag=True, default=False, help="Verbose")
|
||||
@click.option(
|
||||
"--force", is_flag=True, default=False, help="Force build assets instead of downloading available"
|
||||
)
|
||||
@click.option(
|
||||
"--save-metafiles",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Saves esbuild metafiles for built assets. Useful for analyzing bundle size. More info: https://esbuild.github.io/api/#metafile",
|
||||
)
|
||||
def build(
|
||||
app=None,
|
||||
apps=None,
|
||||
hard_link=False,
|
||||
make_copy=False,
|
||||
restore=False,
|
||||
production=False,
|
||||
verbose=False,
|
||||
force=False,
|
||||
save_metafiles=False,
|
||||
):
|
||||
"Compile JS and CSS source files"
|
||||
from frappe.build import bundle, download_frappe_assets
|
||||
|
|
@ -80,14 +69,14 @@ def build(
|
|||
if production:
|
||||
mode = "production"
|
||||
|
||||
if make_copy or restore:
|
||||
hard_link = make_copy or restore
|
||||
click.secho(
|
||||
"bench build: --make-copy and --restore options are deprecated in favour of --hard-link",
|
||||
fg="yellow",
|
||||
)
|
||||
|
||||
bundle(mode, apps=apps, hard_link=hard_link, verbose=verbose, skip_frappe=skip_frappe)
|
||||
bundle(
|
||||
mode,
|
||||
apps=apps,
|
||||
hard_link=hard_link,
|
||||
verbose=verbose,
|
||||
skip_frappe=skip_frappe,
|
||||
save_metafiles=save_metafiles,
|
||||
)
|
||||
|
||||
|
||||
@click.command("watch")
|
||||
|
|
@ -409,37 +398,16 @@ def import_doc(context, path, force=False):
|
|||
raise SiteNotSpecifiedError
|
||||
|
||||
|
||||
@click.command("import-csv", help=DATA_IMPORT_DEPRECATION)
|
||||
@click.argument("path")
|
||||
@click.option(
|
||||
"--only-insert", default=False, is_flag=True, help="Do not overwrite existing records"
|
||||
)
|
||||
@click.option(
|
||||
"--submit-after-import", default=False, is_flag=True, help="Submit document after importing it"
|
||||
)
|
||||
@click.option(
|
||||
"--ignore-encoding-errors",
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help="Ignore encoding errors while coverting to unicode",
|
||||
)
|
||||
@click.option("--no-email", default=True, is_flag=True, help="Send email if applicable")
|
||||
@pass_context
|
||||
def import_csv(
|
||||
context,
|
||||
path,
|
||||
only_insert=False,
|
||||
submit_after_import=False,
|
||||
ignore_encoding_errors=False,
|
||||
no_email=True,
|
||||
):
|
||||
click.secho(DATA_IMPORT_DEPRECATION, fg="yellow")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@click.command("data-import")
|
||||
@click.option(
|
||||
"--file", "file_path", type=click.Path(), required=True, help="Path to import file (.csv, .xlsx)"
|
||||
"--file",
|
||||
"file_path",
|
||||
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
|
||||
required=True,
|
||||
help=(
|
||||
"Path to import file (.csv, .xlsx)."
|
||||
"Consider that relative paths will resolve from 'sites' directory"
|
||||
),
|
||||
)
|
||||
@click.option("--doctype", type=str, required=True)
|
||||
@click.option(
|
||||
|
|
@ -765,7 +733,6 @@ def transform_database(context, table, engine, row_format, failfast):
|
|||
help="Path to .txt file for list of doctypes. Example erpnext/tests/server/agriculture.txt",
|
||||
)
|
||||
@click.option("--test", multiple=True, help="Specific test")
|
||||
@click.option("--ui-tests", is_flag=True, default=False, help="Run UI Tests")
|
||||
@click.option("--module", help="Run tests in a module")
|
||||
@click.option("--profile", is_flag=True, default=False)
|
||||
@click.option("--coverage", is_flag=True, default=False)
|
||||
|
|
@ -788,7 +755,6 @@ def run_tests(
|
|||
profile=False,
|
||||
coverage=False,
|
||||
junit_xml_output=False,
|
||||
ui_tests=False,
|
||||
doctype_list_path=None,
|
||||
skip_test_records=False,
|
||||
skip_before_tests=False,
|
||||
|
|
@ -827,7 +793,6 @@ def run_tests(
|
|||
force=context.force,
|
||||
profile=profile,
|
||||
junit_xml_output=junit_xml_output,
|
||||
ui_tests=ui_tests,
|
||||
doctype_list_path=doctype_list_path,
|
||||
failfast=failfast,
|
||||
case=case,
|
||||
|
|
@ -1063,20 +1028,11 @@ def create_patch():
|
|||
"-g", "--global", "global_", is_flag=True, default=False, help="Set value in bench config"
|
||||
)
|
||||
@click.option("-p", "--parse", is_flag=True, default=False, help="Evaluate as Python Object")
|
||||
@click.option("--as-dict", is_flag=True, default=False, help="Legacy: Evaluate as Python Object")
|
||||
@pass_context
|
||||
def set_config(context, key, value, global_=False, parse=False, as_dict=False):
|
||||
def set_config(context, key, value, global_=False, parse=False):
|
||||
"Insert/Update a value in site_config.json"
|
||||
from frappe.installer import update_site_config
|
||||
|
||||
if as_dict:
|
||||
from frappe.utils.commands import warn
|
||||
|
||||
warn(
|
||||
"--as-dict will be deprecated in v14. Use --parse instead", category=PendingDeprecationWarning
|
||||
)
|
||||
parse = as_dict
|
||||
|
||||
if parse:
|
||||
import ast
|
||||
|
||||
|
|
@ -1200,7 +1156,6 @@ commands = [
|
|||
export_fixtures,
|
||||
export_json,
|
||||
get_version,
|
||||
import_csv,
|
||||
data_import,
|
||||
import_doc,
|
||||
make_app,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2023-06-16 17:57:36.604672",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"action"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "Amend Counter",
|
||||
"fieldname": "action",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Action",
|
||||
"options": "Amend Counter\nDefault Naming",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-16 18:26:16.247475",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Amended Document Naming Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) 2023, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AmendedDocumentNamingSettings(Document):
|
||||
pass
|
||||
|
|
@ -62,7 +62,7 @@ class Importer:
|
|||
|
||||
def before_import(self):
|
||||
# set user lang for translations
|
||||
frappe.cache().hdel("lang", frappe.session.user)
|
||||
frappe.cache.hdel("lang", frappe.session.user)
|
||||
frappe.set_user_lang(frappe.session.user)
|
||||
|
||||
# set flags
|
||||
|
|
@ -579,6 +579,10 @@ class ImportFile:
|
|||
|
||||
file_content = None
|
||||
|
||||
if self.console:
|
||||
file_content = frappe.read_file(file_path, True)
|
||||
return file_content, extn
|
||||
|
||||
file_name = frappe.db.get_value("File", {"file_url": file_path})
|
||||
if file_name:
|
||||
file = frappe.get_doc("File", file_name)
|
||||
|
|
@ -690,7 +694,7 @@ class Row:
|
|||
df = col.df
|
||||
if df.fieldtype == "Select":
|
||||
select_options = get_select_options(df)
|
||||
if select_options and value not in select_options:
|
||||
if select_options and cstr(value) not in select_options:
|
||||
options_string = ", ".join(frappe.bold(d) for d in select_options)
|
||||
msg = _("Value must be one of {0}").format(options_string)
|
||||
self.warnings.append(
|
||||
|
|
@ -1207,7 +1211,7 @@ def get_df_for_column_header(doctype, header):
|
|||
def build_fields_dict_for_doctype():
|
||||
return build_fields_dict_for_column_matching(doctype)
|
||||
|
||||
df_by_labels_and_fieldname = frappe.cache().hget(
|
||||
df_by_labels_and_fieldname = frappe.cache.hget(
|
||||
"data_import_column_header_map", doctype, generator=build_fields_dict_for_doctype
|
||||
)
|
||||
return df_by_labels_and_fieldname.get(header)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"search_index",
|
||||
"column_break_18",
|
||||
"options",
|
||||
"sort_options",
|
||||
"show_dashboard",
|
||||
"defaults_section",
|
||||
"default",
|
||||
|
|
@ -102,7 +103,8 @@
|
|||
"oldfieldtype": "Select",
|
||||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nJSON\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
"search_index": 1,
|
||||
"sort_options": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
|
|
@ -550,13 +552,20 @@
|
|||
"fieldtype": "Data",
|
||||
"label": "Documentation URL",
|
||||
"options": "URL"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.fieldtype === 'Select'",
|
||||
"fieldname": "sort_options",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sort Options"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-02-20 12:07:29.552523",
|
||||
"modified": "2023-06-08 19:05:10.778371",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@
|
|||
"default": "0",
|
||||
"fieldname": "everyone",
|
||||
"fieldtype": "Check",
|
||||
"label": "Everyone"
|
||||
"label": "Everyone",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
|
|
@ -85,10 +86,11 @@
|
|||
],
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-04 11:38:50.813312",
|
||||
"modified": "2023-06-15 18:02:51.877533",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocShare",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
|
@ -106,5 +108,6 @@
|
|||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
0
frappe/core/doctype/doctype/boilerplate/__init__.py
Normal file
0
frappe/core/doctype/doctype/boilerplate/__init__.py
Normal file
|
|
@ -755,4 +755,4 @@
|
|||
"states": [],
|
||||
"track_changes": 1,
|
||||
"translated_doctype": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ class DocType(Document):
|
|||
"DocField", "parent", dict(fieldtype=["in", frappe.model.table_fields], options=self.name)
|
||||
)
|
||||
for p in parent_list:
|
||||
frappe.db.set_value("DocType", p.parent, {}, for_update=False)
|
||||
frappe.db.set_value("DocType", p.parent, {})
|
||||
|
||||
def scrub_field_names(self):
|
||||
"""Sluggify fieldnames if not set from Label."""
|
||||
|
|
@ -1710,7 +1710,7 @@ def check_fieldname_conflicts(docfield):
|
|||
|
||||
|
||||
def clear_linked_doctype_cache():
|
||||
frappe.cache().delete_value("linked_doctypes_without_ignore_user_permissions_enabled")
|
||||
frappe.cache.delete_value("linked_doctypes_without_ignore_user_permissions_enabled")
|
||||
|
||||
|
||||
def check_email_append_to(doc):
|
||||
|
|
|
|||
|
|
@ -2,6 +2,16 @@
|
|||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Document Naming Settings", {
|
||||
setup: function (frm) {
|
||||
frm.set_query("document_type", "amend_naming_override", () => {
|
||||
return {
|
||||
filters: {
|
||||
is_submittable: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.trigger("setup_transaction_autocomplete");
|
||||
frm.disable_save();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,11 @@
|
|||
"update_series",
|
||||
"prefix",
|
||||
"current_value",
|
||||
"update_series_start"
|
||||
"update_series_start",
|
||||
"amended_documents_section",
|
||||
"default_amend_naming",
|
||||
"amend_naming_override",
|
||||
"update_amendment_naming"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -105,13 +109,41 @@
|
|||
"fieldtype": "Text",
|
||||
"label": "Preview of generated names",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"description": "Configure how amended documents will be named.<br>\n\nDefault behaviour is to follow an amend counter which adds a number to the end of the original name indicating the amended version. <br>\n\nDefault Naming will make the amended document to behave same as new documents.",
|
||||
"fieldname": "amended_documents_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Amended Documents"
|
||||
},
|
||||
{
|
||||
"default": "Amend Counter",
|
||||
"fieldname": "default_amend_naming",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Default Amendment Naming",
|
||||
"options": "Amend Counter\nDefault Naming",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amend_naming_override",
|
||||
"fieldtype": "Table",
|
||||
"label": "Amendment Naming Override",
|
||||
"options": "Amended Document Naming Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "update_amendment_naming",
|
||||
"fieldtype": "Button",
|
||||
"label": "Update Amendment Naming",
|
||||
"options": "update_amendment_rule"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "fa fa-sort-by-order",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-02-20 13:11:56.662100",
|
||||
"modified": "2023-06-20 17:47:52.204139",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Document Naming Settings",
|
||||
|
|
|
|||
|
|
@ -169,6 +169,23 @@ class DocumentNamingSettings(Document):
|
|||
self.current_value = NamingSeries(self.prefix).get_current_value()
|
||||
return self.current_value
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_amendment_rule(self):
|
||||
self.db_set("default_amend_naming", self.default_amend_naming)
|
||||
|
||||
existing_overrides = frappe.db.get_all(
|
||||
"Amended Document Naming Settings",
|
||||
filters={"name": ["not in", [d.name for d in self.amend_naming_override]]},
|
||||
pluck="name",
|
||||
)
|
||||
for override in existing_overrides:
|
||||
frappe.delete_doc("Amended Document Naming Settings", override)
|
||||
|
||||
for row in self.amend_naming_override:
|
||||
row.save()
|
||||
|
||||
frappe.msgprint(_("Amendment naming rules updated."), indicator="green", alert=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_series_start(self):
|
||||
frappe.only_for("System Manager")
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class TestNamingSeries(FrappeTestCase):
|
|||
}
|
||||
],
|
||||
autoname="naming_series:",
|
||||
is_submittable=1,
|
||||
)
|
||||
.insert()
|
||||
.name
|
||||
|
|
@ -82,3 +83,36 @@ class TestNamingSeries(FrappeTestCase):
|
|||
self.dns.update_series_start()
|
||||
|
||||
self.assertEqual(self.dns.get_current(), new_count, f"Incorrect update for {series}")
|
||||
|
||||
def test_amended_naming(self):
|
||||
self.dns.amend_naming_override = []
|
||||
self.dns.default_amend_naming = "Amend Counter"
|
||||
self.dns.update_amendment_rule()
|
||||
|
||||
submittable_doc = frappe.get_doc(
|
||||
dict(doctype=self.ns_doctype, some_fieldname="test doc with submit")
|
||||
).submit()
|
||||
submittable_doc.cancel()
|
||||
|
||||
amended_doc = frappe.get_doc(
|
||||
dict(
|
||||
doctype=self.ns_doctype,
|
||||
some_fieldname="test doc with submit",
|
||||
amended_from=submittable_doc.name,
|
||||
)
|
||||
).insert()
|
||||
|
||||
self.assertIn(submittable_doc.name, amended_doc.name)
|
||||
amended_doc.delete()
|
||||
|
||||
self.dns.default_amend_naming = "Default Naming"
|
||||
self.dns.update_amendment_rule()
|
||||
|
||||
new_amended_doc = frappe.get_doc(
|
||||
dict(
|
||||
doctype=self.ns_doctype,
|
||||
some_fieldname="test doc with submit",
|
||||
amended_from=submittable_doc.name,
|
||||
)
|
||||
).insert()
|
||||
self.assertNotIn(submittable_doc.name, new_amended_doc.name)
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ def get_active_domains():
|
|||
active_domains.append("")
|
||||
return active_domains
|
||||
|
||||
return frappe.cache().get_value("active_domains", _get_active_domains)
|
||||
return frappe.cache.get_value("active_domains", _get_active_domains)
|
||||
|
||||
|
||||
def get_active_modules():
|
||||
|
|
@ -87,4 +87,4 @@ def get_active_modules():
|
|||
active_modules.append(m.name)
|
||||
return active_modules
|
||||
|
||||
return frappe.cache().get_value("active_modules", _get_active_modules)
|
||||
return frappe.cache.get_value("active_modules", _get_active_modules)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
# Copyright (c) 2015, Frappe Technologies and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
from ldap3.core.exceptions import LDAPException, LDAPInappropriateAuthenticationResult
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils.error import _is_ldap_exception
|
||||
|
||||
# test_records = frappe.get_test_records('Error Log')
|
||||
|
||||
|
|
@ -12,3 +15,9 @@ class TestErrorLog(FrappeTestCase):
|
|||
doc = frappe.new_doc("Error Log")
|
||||
error = doc.log_error("This is an error")
|
||||
self.assertEqual(error.doctype, "Error Log")
|
||||
|
||||
def test_ldap_exceptions(self):
|
||||
exc = [LDAPException, LDAPInappropriateAuthenticationResult]
|
||||
|
||||
for e in exc:
|
||||
self.assertTrue(_is_ldap_exception(e()))
|
||||
|
|
|
|||
|
|
@ -236,12 +236,19 @@ class File(Document):
|
|||
):
|
||||
return
|
||||
|
||||
frappe.db.set_value(
|
||||
self.attached_to_doctype,
|
||||
self.attached_to_name,
|
||||
self.attached_to_field,
|
||||
self.file_url,
|
||||
)
|
||||
if frappe.get_meta(self.attached_to_doctype).issingle:
|
||||
frappe.db.set_single_value(
|
||||
self.attached_to_doctype,
|
||||
self.attached_to_field,
|
||||
self.file_url,
|
||||
)
|
||||
else:
|
||||
frappe.db.set_value(
|
||||
self.attached_to_doctype,
|
||||
self.attached_to_name,
|
||||
self.attached_to_field,
|
||||
self.file_url,
|
||||
)
|
||||
|
||||
def fetch_attached_to_field(self, old_file_url):
|
||||
if self.attached_to_field:
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class Report(Document):
|
|||
if execution_time > threshold and not self.prepared_report:
|
||||
self.db_set("prepared_report", 1)
|
||||
|
||||
frappe.cache().hset("report_execution_time", self.name, execution_time)
|
||||
frappe.cache.hset("report_execution_time", self.name, execution_time)
|
||||
|
||||
return res
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class Role(Document):
|
|||
frappe.throw(frappe._("Standard roles cannot be renamed"))
|
||||
|
||||
def after_insert(self):
|
||||
frappe.cache().hdel("roles", "Administrator")
|
||||
frappe.cache.hdel("roles", "Administrator")
|
||||
|
||||
def validate(self):
|
||||
if self.disabled:
|
||||
|
|
|
|||
|
|
@ -124,6 +124,20 @@ class TestRQJob(FrappeTestCase):
|
|||
frappe.db.commit()
|
||||
self.assertIsNone(get_job_status(job_id))
|
||||
|
||||
@timeout(20)
|
||||
def test_memory_usage(self):
|
||||
job = frappe.enqueue("frappe.utils.data._get_rss_memory_usage")
|
||||
self.check_status(job, "finished")
|
||||
|
||||
rss = job.latest_result().return_value
|
||||
msg = """Memory usage of simple background job increased. Potential root cause can be a newly added python module import. Check and move them to approriate file/function to avoid loading the module by default."""
|
||||
|
||||
# If this starts failing analyze memory usage using memray or some equivalent tool to find
|
||||
# offending imports/function calls.
|
||||
# Refer this PR: https://github.com/frappe/frappe/pull/21467
|
||||
LAST_MEASURED_USAGE = 40
|
||||
self.assertLessEqual(rss, LAST_MEASURED_USAGE * 1.05, msg)
|
||||
|
||||
|
||||
def test_func(fail=False, sleep=0):
|
||||
if fail:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class TestScheduledJobType(FrappeTestCase):
|
|||
self.assertEqual(all_job.frequency, "All")
|
||||
|
||||
daily_job = frappe.get_doc(
|
||||
"Scheduled Job Type", dict(method="frappe.email.queue.set_expiry_for_email_queue")
|
||||
"Scheduled Job Type", dict(method="frappe.desk.notifications.clear_notifications")
|
||||
)
|
||||
self.assertEqual(daily_job.frequency, "Daily")
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ class TestScheduledJobType(FrappeTestCase):
|
|||
|
||||
def test_daily_job(self):
|
||||
job = frappe.get_doc(
|
||||
"Scheduled Job Type", dict(method="frappe.email.queue.set_expiry_for_email_queue")
|
||||
"Scheduled Job Type", dict(method="frappe.desk.notifications.clear_notifications")
|
||||
)
|
||||
job.db_set("last_execution", "2019-01-01 00:00:00")
|
||||
self.assertTrue(job.is_event_due(get_datetime("2019-01-02 00:00:06")))
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class ServerScript(Document):
|
|||
self.check_if_compilable_in_restricted_context()
|
||||
|
||||
def on_update(self):
|
||||
frappe.cache().delete_value("server_script_map")
|
||||
frappe.cache.delete_value("server_script_map")
|
||||
self.sync_scheduler_events()
|
||||
|
||||
def on_trash(self):
|
||||
|
|
@ -168,11 +168,11 @@ class ServerScript(Document):
|
|||
out.append([key, score])
|
||||
return out
|
||||
|
||||
items = frappe.cache().get_value("server_script_autocompletion_items")
|
||||
items = frappe.cache.get_value("server_script_autocompletion_items")
|
||||
if not items:
|
||||
items = get_keys(get_safe_globals())
|
||||
items = [{"value": d[0], "score": d[1]} for d in items]
|
||||
frappe.cache().set_value("server_script_autocompletion_items", items)
|
||||
frappe.cache.set_value("server_script_autocompletion_items", items)
|
||||
return items
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ def get_server_script_map():
|
|||
if frappe.flags.in_patch and not frappe.db.table_exists("Server Script"):
|
||||
return {}
|
||||
|
||||
script_map = frappe.cache().get_value("server_script_map")
|
||||
script_map = frappe.cache.get_value("server_script_map")
|
||||
if script_map is None:
|
||||
script_map = {"permission_query": {}}
|
||||
enabled_server_scripts = frappe.get_all(
|
||||
|
|
@ -73,6 +73,6 @@ def get_server_script_map():
|
|||
else:
|
||||
script_map.setdefault("_api", {})[script.api_method] = script.name
|
||||
|
||||
frappe.cache().set_value("server_script_map", script_map)
|
||||
frappe.cache.set_value("server_script_map", script_map)
|
||||
|
||||
return script_map
|
||||
|
|
|
|||
|
|
@ -104,10 +104,10 @@ class TestServerScript(FrappeTestCase):
|
|||
def tearDownClass(cls):
|
||||
frappe.db.commit()
|
||||
frappe.db.truncate("Server Script")
|
||||
frappe.cache().delete_value("server_script_map")
|
||||
frappe.cache.delete_value("server_script_map")
|
||||
|
||||
def setUp(self):
|
||||
frappe.cache().delete_value("server_script_map")
|
||||
frappe.cache.delete_value("server_script_map")
|
||||
|
||||
def test_doctype_event(self):
|
||||
todo = frappe.get_doc(dict(doctype="ToDo", description="hello")).insert()
|
||||
|
|
|
|||
|
|
@ -5,14 +5,13 @@ import frappe
|
|||
from frappe import _
|
||||
from frappe.model import no_value_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.translate import set_default_language
|
||||
from frappe.twofactor import toggle_two_factor_auth
|
||||
from frappe.utils import cint, today
|
||||
from frappe.utils.momentjs import get_all_timezones
|
||||
|
||||
|
||||
class SystemSettings(Document):
|
||||
def validate(self):
|
||||
from frappe.twofactor import toggle_two_factor_auth
|
||||
|
||||
enable_password_policy = cint(self.enable_password_policy) and True or False
|
||||
minimum_password_score = cint(getattr(self, "minimum_password_score", 0)) or 0
|
||||
if enable_password_policy and minimum_password_score <= 0:
|
||||
|
|
@ -64,13 +63,15 @@ class SystemSettings(Document):
|
|||
def on_update(self):
|
||||
self.set_defaults()
|
||||
|
||||
frappe.cache().delete_value("system_settings")
|
||||
frappe.cache().delete_value("time_zone")
|
||||
frappe.cache.delete_value("system_settings")
|
||||
frappe.cache.delete_value("time_zone")
|
||||
|
||||
if frappe.flags.update_last_reset_password_date:
|
||||
update_last_reset_password_date()
|
||||
|
||||
def set_defaults(self):
|
||||
from frappe.translate import set_default_language
|
||||
|
||||
for df in self.meta.get("fields"):
|
||||
if df.fieldtype not in no_value_fields and self.has_value_changed(df.fieldname):
|
||||
frappe.db.set_default(df.fieldname, self.get(df.fieldname))
|
||||
|
|
@ -92,6 +93,8 @@ def update_last_reset_password_date():
|
|||
|
||||
@frappe.whitelist()
|
||||
def load():
|
||||
from frappe.utils.momentjs import get_all_timezones
|
||||
|
||||
if not "System Manager" in frappe.get_roles():
|
||||
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,71 +23,7 @@ class Translation(Document):
|
|||
def on_trash(self):
|
||||
clear_user_translation_cache(self.language)
|
||||
|
||||
def contribute(self):
|
||||
pass
|
||||
|
||||
def get_contribution_status(self):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_translations(translation_map, language):
|
||||
from frappe.frappeclient import FrappeClient
|
||||
|
||||
translation_map = json.loads(translation_map)
|
||||
translation_map_to_send = frappe._dict({})
|
||||
# first create / update local user translations
|
||||
for source_id, translation_dict in translation_map.items():
|
||||
translation_dict = frappe._dict(translation_dict)
|
||||
existing_doc_name = frappe.get_all(
|
||||
"Translation",
|
||||
{
|
||||
"source_text": translation_dict.source_text,
|
||||
"context": translation_dict.context or "",
|
||||
"language": language,
|
||||
},
|
||||
)
|
||||
translation_map_to_send[source_id] = translation_dict
|
||||
if existing_doc_name:
|
||||
frappe.db.set_value(
|
||||
"Translation",
|
||||
existing_doc_name[0].name,
|
||||
{
|
||||
"translated_text": translation_dict.translated_text,
|
||||
"contributed": 1,
|
||||
"contribution_status": "Pending",
|
||||
},
|
||||
)
|
||||
translation_map_to_send[source_id].name = existing_doc_name[0].name
|
||||
else:
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Translation",
|
||||
"source_text": translation_dict.source_text,
|
||||
"contributed": 1,
|
||||
"contribution_status": "Pending",
|
||||
"translated_text": translation_dict.translated_text,
|
||||
"context": translation_dict.context,
|
||||
"language": language,
|
||||
}
|
||||
)
|
||||
doc.insert()
|
||||
translation_map_to_send[source_id].name = doc.name
|
||||
|
||||
params = {
|
||||
"language": language,
|
||||
"contributor_email": frappe.session.user,
|
||||
"contributor_name": frappe.utils.get_fullname(frappe.session.user),
|
||||
"translation_map": json.dumps(translation_map_to_send),
|
||||
}
|
||||
|
||||
translator = FrappeClient(get_translator_url())
|
||||
added_translations = translator.post_api("translator.api.add_translations", params=params)
|
||||
|
||||
for local_docname, remote_docname in added_translations.items():
|
||||
frappe.db.set_value("Translation", local_docname, "contribution_docname", remote_docname)
|
||||
|
||||
|
||||
def clear_user_translation_cache(lang):
|
||||
frappe.cache().hdel(USER_TRANSLATION_KEY, lang)
|
||||
frappe.cache().hdel(MERGED_TRANSLATION_KEY, lang)
|
||||
frappe.cache.hdel(USER_TRANSLATION_KEY, lang)
|
||||
frappe.cache.hdel(MERGED_TRANSLATION_KEY, lang)
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ class TestUser(FrappeTestCase):
|
|||
|
||||
# Clear rate limit tracker to start fresh
|
||||
key = f"rl:{data['cmd']}:{data['user']}"
|
||||
frappe.cache().delete(key)
|
||||
frappe.cache.delete(key)
|
||||
|
||||
c = FrappeClient(url)
|
||||
res1 = c.session.post(url, data=data, verify=c.verify, headers=c.headers)
|
||||
|
|
@ -330,7 +330,7 @@ class TestUser(FrappeTestCase):
|
|||
sign_up(random_user, random_user_name, "/welcome"),
|
||||
(1, "Please check your email for verification"),
|
||||
)
|
||||
self.assertEqual(frappe.cache().hget("redirect_after_login", random_user), "/welcome")
|
||||
self.assertEqual(frappe.cache.hget("redirect_after_login", random_user), "/welcome")
|
||||
|
||||
# re-register
|
||||
self.assertTupleEqual(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from frappe.utils import (
|
|||
now_datetime,
|
||||
today,
|
||||
)
|
||||
from frappe.utils.deprecations import deprecated
|
||||
from frappe.utils.password import check_password, get_password_reset_limit
|
||||
from frappe.utils.password import update_password as _update_password
|
||||
from frappe.utils.user import get_system_managers
|
||||
|
|
@ -60,8 +61,8 @@ class User(Document):
|
|||
|
||||
def after_insert(self):
|
||||
create_notification_settings(self.name)
|
||||
frappe.cache().delete_key("users_for_mentions")
|
||||
frappe.cache().delete_key("enabled_users")
|
||||
frappe.cache.delete_key("users_for_mentions")
|
||||
frappe.cache.delete_key("enabled_users")
|
||||
|
||||
def validate(self):
|
||||
# clear new password
|
||||
|
|
@ -75,6 +76,7 @@ class User(Document):
|
|||
self.validate_email_type(self.email)
|
||||
self.validate_email_type(self.name)
|
||||
self.add_system_manager_role()
|
||||
self.populate_role_profile_roles()
|
||||
self.check_roles_added()
|
||||
self.set_system_user()
|
||||
self.set_full_name()
|
||||
|
|
@ -85,7 +87,6 @@ class User(Document):
|
|||
self.remove_disabled_roles()
|
||||
self.validate_user_email_inbox()
|
||||
ask_pass_update()
|
||||
self.validate_roles()
|
||||
self.validate_allowed_modules()
|
||||
self.validate_user_image()
|
||||
self.set_time_zone()
|
||||
|
|
@ -98,12 +99,16 @@ class User(Document):
|
|||
):
|
||||
self.set_social_login_userid("frappe", frappe.generate_hash(length=39))
|
||||
|
||||
def validate_roles(self):
|
||||
def populate_role_profile_roles(self):
|
||||
if self.role_profile_name:
|
||||
role_profile = frappe.get_doc("Role Profile", self.role_profile_name)
|
||||
self.set("roles", [])
|
||||
self.append_roles(*[role.role for role in role_profile.roles])
|
||||
|
||||
@deprecated
|
||||
def validate_roles(self):
|
||||
self.populate_role_profile_roles()
|
||||
|
||||
def validate_allowed_modules(self):
|
||||
if self.module_profile:
|
||||
module_profile = frappe.get_doc("Module Profile", self.module_profile)
|
||||
|
|
@ -143,10 +148,10 @@ class User(Document):
|
|||
frappe.defaults.set_default("time_zone", self.time_zone, self.name)
|
||||
|
||||
if self.has_value_changed("enabled"):
|
||||
frappe.cache().delete_key("users_for_mentions")
|
||||
frappe.cache().delete_key("enabled_users")
|
||||
frappe.cache.delete_key("users_for_mentions")
|
||||
frappe.cache.delete_key("enabled_users")
|
||||
elif self.has_value_changed("allow_in_mentions") or self.has_value_changed("user_type"):
|
||||
frappe.cache().delete_key("users_for_mentions")
|
||||
frappe.cache.delete_key("users_for_mentions")
|
||||
|
||||
def has_website_permission(self, ptype, user, verbose=False):
|
||||
"""Returns true if current user is the session user"""
|
||||
|
|
@ -462,9 +467,9 @@ class User(Document):
|
|||
frappe.delete_doc("Notification Settings", self.name, ignore_permissions=True)
|
||||
|
||||
if self.get("allow_in_mentions"):
|
||||
frappe.cache().delete_key("users_for_mentions")
|
||||
frappe.cache.delete_key("users_for_mentions")
|
||||
|
||||
frappe.cache().delete_key("enabled_users")
|
||||
frappe.cache.delete_key("enabled_users")
|
||||
|
||||
# delete user permissions
|
||||
frappe.db.delete("User Permission", {"user": self.name})
|
||||
|
|
@ -760,10 +765,10 @@ def update_password(
|
|||
user_doc, redirect_url = reset_user_data(user)
|
||||
|
||||
# get redirect url from cache
|
||||
redirect_to = frappe.cache().hget("redirect_after_login", user)
|
||||
redirect_to = frappe.cache.hget("redirect_after_login", user)
|
||||
if redirect_to:
|
||||
redirect_url = redirect_to
|
||||
frappe.cache().hdel("redirect_after_login", user)
|
||||
frappe.cache.hdel("redirect_after_login", user)
|
||||
|
||||
frappe.local.login_manager.login_as(user)
|
||||
|
||||
|
|
@ -921,7 +926,7 @@ def sign_up(email: str, full_name: str, redirect_to: str) -> tuple[int, str]:
|
|||
user.add_roles(default_role)
|
||||
|
||||
if redirect_to:
|
||||
frappe.cache().hset("redirect_after_login", user.name, redirect_to)
|
||||
frappe.cache.hset("redirect_after_login", user.name, redirect_to)
|
||||
|
||||
if user.flags.email_sent:
|
||||
return 1, _("Please check your email for verification")
|
||||
|
|
@ -1234,4 +1239,4 @@ def get_enabled_users():
|
|||
enabled_users = frappe.get_all("User", filters={"enabled": "1"}, pluck="name")
|
||||
return enabled_users
|
||||
|
||||
return frappe.cache().get_value("enabled_users", _get_enabled_users)
|
||||
return frappe.cache.get_value("enabled_users", _get_enabled_users)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from frappe.model.document import Document
|
|||
|
||||
class UserGroup(Document):
|
||||
def after_insert(self):
|
||||
frappe.cache().delete_key("user_groups")
|
||||
frappe.cache.delete_key("user_groups")
|
||||
|
||||
def on_trash(self):
|
||||
frappe.cache().delete_key("user_groups")
|
||||
frappe.cache.delete_key("user_groups")
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ class TestUserPermission(FrappeTestCase):
|
|||
frappe.db.set_value(
|
||||
"User Permission", {"allow": "Person", "for_value": parent_record.name}, "hide_descendants", 1
|
||||
)
|
||||
frappe.cache().delete_value("user_permissions")
|
||||
frappe.cache.delete_value("user_permissions")
|
||||
|
||||
# check if adding perm on a group record with hide_descendants enabled,
|
||||
# hides child records
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ class UserPermission(Document):
|
|||
self.validate_default_permission()
|
||||
|
||||
def on_update(self):
|
||||
frappe.cache().hdel("user_permissions", self.user)
|
||||
frappe.cache.hdel("user_permissions", self.user)
|
||||
frappe.publish_realtime("update_user_permissions", user=self.user, after_commit=True)
|
||||
|
||||
def on_trash(self):
|
||||
frappe.cache().hdel("user_permissions", self.user)
|
||||
frappe.cache.hdel("user_permissions", self.user)
|
||||
frappe.publish_realtime("update_user_permissions", user=self.user, after_commit=True)
|
||||
|
||||
def validate_user_permission(self):
|
||||
|
|
@ -74,7 +74,7 @@ def get_user_permissions(user=None):
|
|||
if not user or user in ("Administrator", "Guest"):
|
||||
return {}
|
||||
|
||||
cached_user_permissions = frappe.cache().hget("user_permissions", user)
|
||||
cached_user_permissions = frappe.cache.hget("user_permissions", user)
|
||||
|
||||
if cached_user_permissions is not None:
|
||||
return cached_user_permissions
|
||||
|
|
@ -110,7 +110,7 @@ def get_user_permissions(user=None):
|
|||
add_doc_to_perm(perm, doc, False)
|
||||
|
||||
out = frappe._dict(out)
|
||||
frappe.cache().hset("user_permissions", user, out)
|
||||
frappe.cache.hset("user_permissions", user, out)
|
||||
except frappe.db.SQLError as e:
|
||||
if frappe.db.is_table_missing(e):
|
||||
# called from patch
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class UserType(Document):
|
|||
super().clear_cache()
|
||||
|
||||
if not self.is_standard:
|
||||
frappe.cache().delete_value("non_standard_user_types")
|
||||
frappe.cache.delete_value("non_standard_user_types")
|
||||
|
||||
def on_update(self):
|
||||
if self.is_standard:
|
||||
|
|
@ -290,7 +290,7 @@ def apply_permissions_for_non_standard_user_type(doc, method=None):
|
|||
if not frappe.db.table_exists("User Type") or frappe.flags.in_migrate:
|
||||
return
|
||||
|
||||
user_types = frappe.cache().get_value(
|
||||
user_types = frappe.cache.get_value(
|
||||
"non_standard_user_types",
|
||||
get_non_standard_user_types,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -123,8 +123,15 @@ def update(doctype, role, permlevel, ptype, value=None):
|
|||
Returns:
|
||||
str: Refresh flag is permission is updated successfully
|
||||
"""
|
||||
|
||||
def clear_cache():
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
|
||||
frappe.only_for("System Manager")
|
||||
out = update_permission_property(doctype, role, permlevel, ptype, value)
|
||||
|
||||
frappe.db.after_commit.add(clear_cache)
|
||||
|
||||
return "refresh" if out else None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"hide_seconds",
|
||||
"hide_days",
|
||||
"options",
|
||||
"sort_options",
|
||||
"fetch_from",
|
||||
"fetch_if_empty",
|
||||
"options_help",
|
||||
|
|
@ -126,7 +127,8 @@
|
|||
"oldfieldname": "fieldtype",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nJSON\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"sort_options": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
|
|
@ -435,13 +437,20 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Is System Generated",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.fieldtype === 'Select'",
|
||||
"fieldname": "sort_options",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sort Options"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-13 06:39:03.319667",
|
||||
"modified": "2023-06-08 19:05:51.737234",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Field",
|
||||
|
|
|
|||
|
|
@ -40,8 +40,9 @@ class CustomField(Document):
|
|||
|
||||
# remove special characters from fieldname
|
||||
self.fieldname = "".join(
|
||||
filter(lambda x: x.isdigit() or x.isalpha() or "_", cstr(label).replace(" ", "_"))
|
||||
[c for c in cstr(label).replace(" ", "_") if c.isdigit() or c.isalpha() or c == "_"]
|
||||
)
|
||||
self.fieldname = f"custom_{self.fieldname}"
|
||||
|
||||
# fieldnames should be lowercase
|
||||
self.fieldname = self.fieldname.lower()
|
||||
|
|
|
|||
|
|
@ -49,14 +49,6 @@ frappe.ui.form.on("Customize Form", {
|
|||
grid_row.row.addClass("highlight");
|
||||
}
|
||||
});
|
||||
|
||||
$(frm.wrapper).on("grid-make-sortable", function (e, frm) {
|
||||
frm.trigger("setup_sortable");
|
||||
});
|
||||
|
||||
$(frm.wrapper).on("grid-move-row", function (e, frm) {
|
||||
frm.trigger("setup_sortable");
|
||||
});
|
||||
},
|
||||
|
||||
doc_type: function (frm) {
|
||||
|
|
@ -71,7 +63,7 @@ frappe.ui.form.on("Customize Form", {
|
|||
frm.set_value("doc_type", "");
|
||||
} else {
|
||||
frm.refresh();
|
||||
frm.trigger("setup_sortable");
|
||||
frm.trigger("add_customize_child_table_button");
|
||||
frm.trigger("setup_default_views");
|
||||
}
|
||||
}
|
||||
|
|
@ -87,23 +79,16 @@ frappe.ui.form.on("Customize Form", {
|
|||
frm.trigger("setup_default_views");
|
||||
},
|
||||
|
||||
setup_sortable: function (frm) {
|
||||
add_customize_child_table_button: function (frm) {
|
||||
frm.doc.fields.forEach(function (f) {
|
||||
if (!f.is_custom_field || f.is_system_generated) {
|
||||
f._sortable = false;
|
||||
}
|
||||
if (!in_list(["Table", "Table MultiSelect"], f.fieldtype)) return;
|
||||
|
||||
if (f.fieldtype == "Table") {
|
||||
frm.add_custom_button(
|
||||
f.options,
|
||||
function () {
|
||||
frm.set_value("doc_type", f.options);
|
||||
},
|
||||
__("Customize Child Table")
|
||||
);
|
||||
}
|
||||
frm.add_custom_button(
|
||||
f.options,
|
||||
() => frm.set_value("doc_type", f.options),
|
||||
__("Customize Child Table")
|
||||
);
|
||||
});
|
||||
frm.fields_dict.fields.grid.refresh();
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
|
@ -125,6 +110,14 @@ frappe.ui.form.on("Customize Form", {
|
|||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Set Permissions"),
|
||||
function () {
|
||||
frappe.set_route("permission-manager", frm.doc.doc_type);
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Reload"),
|
||||
function () {
|
||||
|
|
@ -134,17 +127,17 @@ frappe.ui.form.on("Customize Form", {
|
|||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Reset to defaults"),
|
||||
function () {
|
||||
frappe.customize_form.confirm(__("Remove all customizations?"), frm);
|
||||
__("Reset Layout"),
|
||||
() => {
|
||||
frm.trigger("reset_layout");
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Set Permissions"),
|
||||
__("Reset All Customizations"),
|
||||
function () {
|
||||
frappe.set_route("permission-manager", frm.doc.doc_type);
|
||||
frappe.customize_form.confirm(__("Remove all customizations?"), frm);
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
|
|
@ -179,6 +172,27 @@ frappe.ui.form.on("Customize Form", {
|
|||
}
|
||||
},
|
||||
|
||||
reset_layout(frm) {
|
||||
frappe.confirm(
|
||||
__("Layout will be reset to standard layout, are you sure you want to do this?"),
|
||||
() => {
|
||||
return frm.call({
|
||||
doc: frm.doc,
|
||||
method: "reset_layout",
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
frappe.show_alert({
|
||||
message: __("Layout Reset"),
|
||||
indicator: "green",
|
||||
});
|
||||
frappe.customize_form.clear_locals_and_refresh(frm);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
setup_export(frm) {
|
||||
if (frappe.boot.developer_mode) {
|
||||
frm.add_custom_button(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class CustomizeForm(Document):
|
|||
if not self.doc_type:
|
||||
return
|
||||
|
||||
meta = frappe.get_meta(self.doc_type)
|
||||
meta = frappe.get_meta(self.doc_type, cached=False)
|
||||
|
||||
self.validate_doctype(meta)
|
||||
|
||||
|
|
@ -214,11 +214,39 @@ class CustomizeForm(Document):
|
|||
# action and links
|
||||
self.set_property_setters_for_actions_and_links(meta)
|
||||
|
||||
def set_property_setter_for_field_order(self, meta):
|
||||
new_order = [df.fieldname for df in self.fields]
|
||||
existing_order = getattr(meta, "field_order", None)
|
||||
default_order = [
|
||||
fieldname for fieldname, df in meta._fields.items() if not getattr(df, "is_custom_field", False)
|
||||
]
|
||||
|
||||
if new_order == default_order:
|
||||
if existing_order:
|
||||
delete_property_setter(self.doc_type, "field_order")
|
||||
|
||||
return
|
||||
|
||||
if existing_order and new_order == json.loads(existing_order):
|
||||
return
|
||||
|
||||
frappe.make_property_setter(
|
||||
{
|
||||
"doctype": self.doc_type,
|
||||
"doctype_or_field": "DocType",
|
||||
"property": "field_order",
|
||||
"value": json.dumps(new_order),
|
||||
},
|
||||
is_system_generated=False,
|
||||
)
|
||||
|
||||
def set_property_setters_for_doctype(self, meta):
|
||||
for prop, prop_type in doctype_properties.items():
|
||||
if self.get(prop) != meta.get(prop):
|
||||
self.make_property_setter(prop, self.get(prop), prop_type)
|
||||
|
||||
self.set_property_setter_for_field_order(meta)
|
||||
|
||||
def set_property_setters_for_docfield(self, meta, df, meta_df):
|
||||
for prop, prop_type in docfield_properties.items():
|
||||
if prop != "idx" and (df.get(prop) or "") != (meta_df[0].get(prop) or ""):
|
||||
|
|
@ -540,6 +568,24 @@ class CustomizeForm(Document):
|
|||
reset_customization(self.doc_type)
|
||||
self.fetch_to_customize()
|
||||
|
||||
@frappe.whitelist()
|
||||
def reset_layout(self):
|
||||
if not self.doc_type:
|
||||
return
|
||||
|
||||
property_setters = frappe.get_all(
|
||||
"Property Setter",
|
||||
filters={"doc_type": self.doc_type, "property": ("in", ("field_order", "insert_after"))},
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
if not property_setters:
|
||||
return
|
||||
|
||||
frappe.db.delete("Property Setter", {"name": ("in", property_setters)})
|
||||
frappe.clear_cache(doctype=self.doc_type)
|
||||
self.fetch_to_customize()
|
||||
|
||||
@classmethod
|
||||
def allow_fieldtype_change(self, old_type: str, new_type: str) -> bool:
|
||||
"""allow type change, if both old_type and new_type are in same field group.
|
||||
|
|
@ -619,6 +665,7 @@ docfield_properties = {
|
|||
"label": "Data",
|
||||
"fieldtype": "Select",
|
||||
"options": "Text",
|
||||
"sort_options": "Check",
|
||||
"fetch_from": "Small Text",
|
||||
"fetch_if_empty": "Check",
|
||||
"show_dashboard": "Check",
|
||||
|
|
|
|||
|
|
@ -14,10 +14,11 @@ test_dependencies = ["Custom Field", "Property Setter"]
|
|||
|
||||
class TestCustomizeForm(FrappeTestCase):
|
||||
def insert_custom_field(self):
|
||||
frappe.delete_doc_if_exists("Custom Field", "Event-test_custom_field")
|
||||
frappe.get_doc(
|
||||
frappe.delete_doc_if_exists("Custom Field", "Event-custom_test_field")
|
||||
self.field = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Custom Field",
|
||||
"fieldname": "custom_test_field",
|
||||
"dt": "Event",
|
||||
"label": "Test Custom Field",
|
||||
"description": "A Custom Field for Testing",
|
||||
|
|
@ -36,7 +37,7 @@ class TestCustomizeForm(FrappeTestCase):
|
|||
frappe.clear_cache(doctype="Event")
|
||||
|
||||
def tearDown(self):
|
||||
frappe.delete_doc("Custom Field", "Event-test_custom_field")
|
||||
frappe.delete_doc("Custom Field", self.field.name)
|
||||
frappe.db.commit()
|
||||
frappe.clear_cache(doctype="Event")
|
||||
|
||||
|
|
@ -60,7 +61,7 @@ class TestCustomizeForm(FrappeTestCase):
|
|||
self.assertEqual(d.doc_type, "Event")
|
||||
|
||||
self.assertEqual(len(d.get("fields")), len(frappe.get_doc("DocType", d.doc_type).fields) + 1)
|
||||
self.assertEqual(d.get("fields")[-1].fieldname, "test_custom_field")
|
||||
self.assertEqual(d.get("fields")[-1].fieldname, self.field.fieldname)
|
||||
self.assertEqual(d.get("fields", {"fieldname": "event_type"})[0].in_list_view, 1)
|
||||
|
||||
return d
|
||||
|
|
@ -129,21 +130,21 @@ class TestCustomizeForm(FrappeTestCase):
|
|||
|
||||
def test_save_customization_custom_field_property(self):
|
||||
d = self.get_customize_form("Event")
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 0)
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "reqd"), 0)
|
||||
|
||||
custom_field = d.get("fields", {"fieldname": "test_custom_field"})[0]
|
||||
custom_field = d.get("fields", {"fieldname": self.field.fieldname})[0]
|
||||
custom_field.reqd = 1
|
||||
custom_field.no_copy = 1
|
||||
d.run_method("save_customization")
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 1)
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "no_copy"), 1)
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "reqd"), 1)
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "no_copy"), 1)
|
||||
|
||||
custom_field = d.get("fields", {"is_custom_field": True})[0]
|
||||
custom_field.reqd = 0
|
||||
custom_field.no_copy = 0
|
||||
d.run_method("save_customization")
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 0)
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "no_copy"), 0)
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "reqd"), 0)
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "no_copy"), 0)
|
||||
|
||||
def test_save_customization_new_field(self):
|
||||
d = self.get_customize_form("Event")
|
||||
|
|
@ -157,28 +158,24 @@ class TestCustomizeForm(FrappeTestCase):
|
|||
},
|
||||
)
|
||||
d.run_method("save_customization")
|
||||
|
||||
custom_field_name = "Event-custom_test_add_custom_field_via_customize_form"
|
||||
self.assertEqual(
|
||||
frappe.db.get_value(
|
||||
"Custom Field", "Event-test_add_custom_field_via_customize_form", "fieldtype"
|
||||
),
|
||||
frappe.db.get_value("Custom Field", custom_field_name, "fieldtype"),
|
||||
"Data",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value(
|
||||
"Custom Field", "Event-test_add_custom_field_via_customize_form", "insert_after"
|
||||
),
|
||||
frappe.db.get_value("Custom Field", custom_field_name, "insert_after"),
|
||||
last_fieldname,
|
||||
)
|
||||
|
||||
frappe.delete_doc("Custom Field", "Event-test_add_custom_field_via_customize_form")
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Custom Field", "Event-test_add_custom_field_via_customize_form"), None
|
||||
)
|
||||
frappe.delete_doc("Custom Field", custom_field_name)
|
||||
self.assertEqual(frappe.db.get_value("Custom Field", custom_field_name), None)
|
||||
|
||||
def test_save_customization_remove_field(self):
|
||||
d = self.get_customize_form("Event")
|
||||
custom_field = d.get("fields", {"fieldname": "test_custom_field"})[0]
|
||||
custom_field = d.get("fields", {"fieldname": self.field.fieldname})[0]
|
||||
d.get("fields").remove(custom_field)
|
||||
d.run_method("save_customization")
|
||||
|
||||
|
|
@ -200,7 +197,7 @@ class TestCustomizeForm(FrappeTestCase):
|
|||
def test_set_allow_on_submit(self):
|
||||
d = self.get_customize_form("Event")
|
||||
d.get("fields", {"fieldname": "subject"})[0].allow_on_submit = 1
|
||||
d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit = 1
|
||||
d.get("fields", {"fieldname": "custom_test_field"})[0].allow_on_submit = 1
|
||||
d.run_method("save_customization")
|
||||
|
||||
d = self.get_customize_form("Event")
|
||||
|
|
@ -209,7 +206,7 @@ class TestCustomizeForm(FrappeTestCase):
|
|||
self.assertEqual(d.get("fields", {"fieldname": "subject"})[0].allow_on_submit or 0, 0)
|
||||
|
||||
# allow for custom field
|
||||
self.assertEqual(d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit, 1)
|
||||
self.assertEqual(d.get("fields", {"fieldname": "custom_test_field"})[0].allow_on_submit, 1)
|
||||
|
||||
def test_title_field_pattern(self):
|
||||
d = self.get_customize_form("Web Form")
|
||||
|
|
@ -406,7 +403,7 @@ class TestCustomizeForm(FrappeTestCase):
|
|||
|
||||
def test_system_generated_fields(self):
|
||||
doctype = "Event"
|
||||
custom_field_name = "test_custom_field"
|
||||
custom_field_name = "custom_test_field"
|
||||
|
||||
custom_field = frappe.get_doc("Custom Field", {"dt": doctype, "fieldname": custom_field_name})
|
||||
custom_field.is_system_generated = 1
|
||||
|
|
@ -425,3 +422,15 @@ class TestCustomizeForm(FrappeTestCase):
|
|||
self.assertEqual(
|
||||
frappe.db.get_value("Property Setter", property_setter_filters, "value"), "Test Description"
|
||||
)
|
||||
|
||||
def test_custom_field_order(self):
|
||||
# shuffle fields
|
||||
customize_form = self.get_customize_form(doctype="ToDo")
|
||||
customize_form.fields.insert(0, customize_form.fields.pop())
|
||||
customize_form.save_customization()
|
||||
|
||||
field_order_property = json.loads(
|
||||
frappe.db.get_value("Property Setter", {"doc_type": "ToDo", "property": "field_order"}, "value")
|
||||
)
|
||||
|
||||
self.assertEqual(field_order_property, [df.fieldname for df in frappe.get_meta("ToDo").fields])
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
"precision",
|
||||
"length",
|
||||
"options",
|
||||
"sort_options",
|
||||
"fetch_from",
|
||||
"fetch_if_empty",
|
||||
"show_dashboard",
|
||||
|
|
@ -89,7 +90,8 @@
|
|||
"oldfieldtype": "Select",
|
||||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
"search_index": 1,
|
||||
"sort_options": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
|
|
@ -462,13 +464,20 @@
|
|||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.fieldtype === 'Select'",
|
||||
"fieldname": "sort_options",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sort Options"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-02-20 12:07:40.242470",
|
||||
"modified": "2023-06-08 19:05:37.767838",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ from pypika.terms import Criterion, NullValue
|
|||
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
import frappe.model.meta
|
||||
from frappe import _
|
||||
from frappe.database.utils import (
|
||||
DefaultOrderBy,
|
||||
|
|
@ -33,7 +32,7 @@ from frappe.query_builder.functions import Count
|
|||
from frappe.utils import CallbackManager
|
||||
from frappe.utils import cast as cast_fieldtype
|
||||
from frappe.utils import cint, get_datetime, get_table_name, getdate, now, sbool
|
||||
from frappe.utils.deprecations import deprecated, deprecation_warning
|
||||
from frappe.utils.deprecations import deprecation_warning
|
||||
|
||||
IFNULL_PATTERN = re.compile(r"ifnull\(", flags=re.IGNORECASE)
|
||||
INDEX_PATTERN = re.compile(r"\s*\([^)]+\)\s*")
|
||||
|
|
@ -302,7 +301,7 @@ class Database:
|
|||
"""Takes the query and logs it to various interfaces according to the settings."""
|
||||
_query = None
|
||||
|
||||
if frappe.conf.allow_tests and frappe.cache().get_value("flag_print_sql"):
|
||||
if frappe.conf.allow_tests and frappe.cache.get_value("flag_print_sql"):
|
||||
_query = _query or str(mogrified_query)
|
||||
print(_query)
|
||||
|
||||
|
|
@ -419,7 +418,7 @@ class Database:
|
|||
@staticmethod
|
||||
def clear_db_table_cache(query):
|
||||
if query and is_query_type(query, ("drop", "create")):
|
||||
frappe.cache().delete_key("db_tables")
|
||||
frappe.cache.delete_key("db_tables")
|
||||
|
||||
def get_description(self):
|
||||
"""Returns result metadata."""
|
||||
|
|
@ -874,7 +873,6 @@ class Database:
|
|||
modified_by=None,
|
||||
update_modified=True,
|
||||
debug=False,
|
||||
for_update=True,
|
||||
):
|
||||
"""Set a single value in the database, do not call the ORM triggers
|
||||
but update the modified timestamp (unless specified not to).
|
||||
|
|
@ -890,10 +888,11 @@ class Database:
|
|||
:param update_modified: default True. Set as false, if you don't want to update the timestamp.
|
||||
:param debug: Print the query in the developer / js console.
|
||||
"""
|
||||
from frappe.model.utils import is_single_doctype
|
||||
|
||||
if _is_single_doctype := not (dn and dt != dn):
|
||||
if (dn is None or dt == dn) and is_single_doctype(dt):
|
||||
deprecation_warning(
|
||||
"Calling db.set_value on single doctype is deprecated. This behaviour will be removed in version 15. Use db.set_single_value instead."
|
||||
"Calling db.set_value on single doctype is deprecated. This behaviour will be removed in future. Use db.set_single_value instead."
|
||||
)
|
||||
self.set_single_value(
|
||||
doctype=dt,
|
||||
|
|
@ -1067,7 +1066,7 @@ class Database:
|
|||
def count(self, dt, filters=None, debug=False, cache=False, distinct: bool = True):
|
||||
"""Returns `COUNT(*)` for given DocType and filters."""
|
||||
if cache and not filters:
|
||||
cache_count = frappe.cache().get_value(f"doctype:count:{dt}")
|
||||
cache_count = frappe.cache.get_value(f"doctype:count:{dt}")
|
||||
if cache_count is not None:
|
||||
return cache_count
|
||||
count = frappe.qb.get_query(
|
||||
|
|
@ -1078,7 +1077,7 @@ class Database:
|
|||
validate_filters=True,
|
||||
).run(debug=debug)[0][0]
|
||||
if not filters and cache:
|
||||
frappe.cache().set_value(f"doctype:count:{dt}", count, expires_in_sec=86400)
|
||||
frappe.cache.set_value(f"doctype:count:{dt}", count, expires_in_sec=86400)
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -1109,7 +1108,7 @@ class Database:
|
|||
|
||||
def get_db_table_columns(self, table) -> list[str]:
|
||||
"""Returns list of column names from given table."""
|
||||
columns = frappe.cache().hget("table_columns", table)
|
||||
columns = frappe.cache.hget("table_columns", table)
|
||||
if columns is None:
|
||||
information_schema = frappe.qb.Schema("information_schema")
|
||||
|
||||
|
|
@ -1121,7 +1120,7 @@ class Database:
|
|||
)
|
||||
|
||||
if columns:
|
||||
frappe.cache().hset("table_columns", table, columns)
|
||||
frappe.cache.hset("table_columns", table, columns)
|
||||
|
||||
return columns
|
||||
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
|||
to_query = not cached
|
||||
|
||||
if cached:
|
||||
tables = frappe.cache().get_value("db_tables")
|
||||
tables = frappe.cache.get_value("db_tables")
|
||||
to_query = not tables
|
||||
|
||||
if to_query:
|
||||
|
|
@ -447,7 +447,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
|||
.where(information_schema.tables.table_schema != "information_schema")
|
||||
.run(pluck=True)
|
||||
)
|
||||
frappe.cache().set_value("db_tables", tables)
|
||||
frappe.cache.set_value("db_tables", tables)
|
||||
|
||||
return tables
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class DBTable:
|
|||
if self.is_new():
|
||||
self.create()
|
||||
else:
|
||||
frappe.cache().hdel("table_columns", self.table_name)
|
||||
frappe.cache.hdel("table_columns", self.table_name)
|
||||
self.alter()
|
||||
|
||||
def create(self):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import frappe
|
||||
from frappe.cache_manager import clear_defaults_cache, common_default_keys
|
||||
from frappe.desk.notifications import clear_notifications
|
||||
from frappe.query_builder import DocType
|
||||
|
||||
# Note: DefaultValue records are identified by parent (e.g. __default, __global)
|
||||
|
|
@ -230,7 +229,7 @@ def clear_default(key=None, value=None, parent=None, name=None, parenttype=None)
|
|||
|
||||
def get_defaults_for(parent="__default"):
|
||||
"""get all defaults"""
|
||||
defaults = frappe.cache().hget("defaults", parent)
|
||||
defaults = frappe.cache.hget("defaults", parent)
|
||||
|
||||
if defaults is None:
|
||||
# sort descending because first default must get precedence
|
||||
|
|
@ -256,7 +255,7 @@ def get_defaults_for(parent="__default"):
|
|||
elif d.defvalue is not None:
|
||||
defaults[d.defkey] = d.defvalue
|
||||
|
||||
frappe.cache().hset("defaults", parent, defaults)
|
||||
frappe.cache.hset("defaults", parent, defaults)
|
||||
|
||||
return defaults
|
||||
|
||||
|
|
|
|||
|
|
@ -19,20 +19,20 @@ def deferred_insert(doctype: str, records: list[Union[dict, "Document"]] | str):
|
|||
_records = records
|
||||
|
||||
try:
|
||||
frappe.cache().rpush(f"{queue_prefix}{doctype}", _records)
|
||||
frappe.cache.rpush(f"{queue_prefix}{doctype}", _records)
|
||||
except redis.exceptions.ConnectionError:
|
||||
for record in records:
|
||||
insert_record(record, doctype)
|
||||
|
||||
|
||||
def save_to_db():
|
||||
queue_keys = frappe.cache().get_keys(queue_prefix)
|
||||
queue_keys = frappe.cache.get_keys(queue_prefix)
|
||||
for key in queue_keys:
|
||||
record_count = 0
|
||||
queue_key = get_key_name(key)
|
||||
doctype = get_doctype_name(key)
|
||||
while frappe.cache().llen(queue_key) > 0 and record_count <= 500:
|
||||
records = frappe.cache().lpop(queue_key)
|
||||
while frappe.cache.llen(queue_key) > 0 and record_count <= 500:
|
||||
records = frappe.cache.lpop(queue_key)
|
||||
records = json.loads(records.decode("utf-8"))
|
||||
if isinstance(records, dict):
|
||||
record_count += 1
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ class Workspace:
|
|||
|
||||
self.table_counts = get_table_with_counts()
|
||||
self.restricted_doctypes = (
|
||||
frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache()
|
||||
frappe.cache.get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache()
|
||||
)
|
||||
self.restricted_pages = (
|
||||
frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache()
|
||||
frappe.cache.get_value("domain_restricted_pages") or build_domain_restriced_page_cache()
|
||||
)
|
||||
|
||||
def is_permitted(self):
|
||||
|
|
@ -88,16 +88,14 @@ class Workspace:
|
|||
return True
|
||||
|
||||
def get_cached(self, cache_key, fallback_fn):
|
||||
_cache = frappe.cache()
|
||||
|
||||
value = _cache.get_value(cache_key, user=frappe.session.user)
|
||||
value = frappe.cache.get_value(cache_key, user=frappe.session.user)
|
||||
if value:
|
||||
return value
|
||||
|
||||
value = fallback_fn()
|
||||
|
||||
# Expire every six hour
|
||||
_cache.set_value(cache_key, value, frappe.session.user, 21600)
|
||||
frappe.cache.set_value(cache_key, value, frappe.session.user, 21600)
|
||||
return value
|
||||
|
||||
def get_can_read_items(self):
|
||||
|
|
@ -469,7 +467,7 @@ def get_workspace_sidebar_items():
|
|||
|
||||
|
||||
def get_table_with_counts():
|
||||
counts = frappe.cache().get_value("information_schema:counts")
|
||||
counts = frappe.cache.get_value("information_schema:counts")
|
||||
if not counts:
|
||||
counts = build_table_count_cache()
|
||||
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ def get_charts_for_user(doctype, txt, searchfield, start, page_len, filters):
|
|||
|
||||
class DashboardChart(Document):
|
||||
def on_update(self):
|
||||
frappe.cache().delete_key(f"chart-data:{self.name}")
|
||||
frappe.cache.delete_key(f"chart-data:{self.name}")
|
||||
if frappe.conf.developer_mode and self.is_standard:
|
||||
export_to_files(record_list=[["Dashboard Chart", self.name]], record_module=self.module)
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ def get_desktop_icons(user=None):
|
|||
if not user:
|
||||
user = frappe.session.user
|
||||
|
||||
user_icons = frappe.cache().hget("desktop_icons", user)
|
||||
user_icons = frappe.cache.hget("desktop_icons", user)
|
||||
|
||||
if not user_icons:
|
||||
fields = [
|
||||
|
|
@ -120,7 +120,7 @@ def get_desktop_icons(user=None):
|
|||
if d.label:
|
||||
d.label = _(d.label)
|
||||
|
||||
frappe.cache().hset("desktop_icons", user, user_icons)
|
||||
frappe.cache.hset("desktop_icons", user, user_icons)
|
||||
|
||||
return user_icons
|
||||
|
||||
|
|
@ -313,8 +313,8 @@ def get_all_icons():
|
|||
|
||||
|
||||
def clear_desktop_icons_cache(user=None):
|
||||
frappe.cache().hdel("desktop_icons", user or frappe.session.user)
|
||||
frappe.cache().hdel("bootinfo", user or frappe.session.user)
|
||||
frappe.cache.hdel("desktop_icons", user or frappe.session.user)
|
||||
frappe.cache.hdel("bootinfo", user or frappe.session.user)
|
||||
|
||||
|
||||
def get_user_copy(module_name, user=None):
|
||||
|
|
@ -445,7 +445,7 @@ def get_module_icons(user=None):
|
|||
if not user:
|
||||
icons = frappe.get_all("Desktop Icon", fields="*", filters={"standard": 1}, order_by="idx")
|
||||
else:
|
||||
frappe.cache().hdel("desktop_icons", user)
|
||||
frappe.cache.hdel("desktop_icons", user)
|
||||
icons = get_user_icons(user)
|
||||
|
||||
for icon in icons:
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ class FormTour(Document):
|
|||
step.fieldtype = field_df.fieldtype
|
||||
|
||||
def on_update(self):
|
||||
frappe.cache().delete_key("bootinfo")
|
||||
frappe.cache.delete_key("bootinfo")
|
||||
|
||||
if frappe.conf.developer_mode and self.is_standard:
|
||||
export_to_files([["Form Tour", self.name]], self.module)
|
||||
|
||||
def on_trash(self):
|
||||
frappe.cache().delete_key("bootinfo")
|
||||
frappe.cache.delete_key("bootinfo")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
@ -51,7 +51,7 @@ def reset_tour(tour_name):
|
|||
frappe.db.set_value(
|
||||
"User", user, "onboarding_status", frappe.as_json(onboarding_status), update_modified=False
|
||||
)
|
||||
frappe.cache().hdel("bootinfo", user)
|
||||
frappe.cache.hdel("bootinfo", user)
|
||||
|
||||
frappe.msgprint(_("Successfully reset onboarding status for all users."), alert=True)
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ def update_user_status(value, step):
|
|||
"User", frappe.session.user, "onboarding_status", value, update_modified=False
|
||||
)
|
||||
|
||||
frappe.cache().hdel("bootinfo", frappe.session.user)
|
||||
frappe.cache.hdel("bootinfo", frappe.session.user)
|
||||
|
||||
|
||||
def get_onboarding_ui_tours():
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class GlobalSearchSettings(Document):
|
|||
frappe.throw(_("Document Type {0} has been repeated.").format(repeated_dts))
|
||||
|
||||
# reset cache
|
||||
frappe.cache().hdel("global_search", "search_priorities")
|
||||
frappe.cache.hdel("global_search", "search_priorities")
|
||||
|
||||
|
||||
def get_doctypes_for_global_search():
|
||||
|
|
@ -36,7 +36,7 @@ def get_doctypes_for_global_search():
|
|||
doctypes = frappe.get_all("Global Search DocType", fields=["document_type"], order_by="idx ASC")
|
||||
return [d.document_type for d in doctypes] or []
|
||||
|
||||
return frappe.cache().hget("global_search", "search_priorities", get_from_db)
|
||||
return frappe.cache.hget("global_search", "search_priorities", get_from_db)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class KanbanBoard(Document):
|
|||
|
||||
def on_change(self):
|
||||
frappe.clear_cache(doctype=self.reference_doctype)
|
||||
frappe.cache().delete_keys("_user_settings")
|
||||
frappe.cache.delete_keys("_user_settings")
|
||||
|
||||
def before_insert(self):
|
||||
for column in self.columns:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ frappe.ui.form.on("Module Onboarding", {
|
|||
if (!frappe.boot.developer_mode) {
|
||||
frm.trigger("disable_form");
|
||||
}
|
||||
|
||||
frm.add_custom_button(__("Reset"), () => {
|
||||
frm.call("reset_progress");
|
||||
});
|
||||
},
|
||||
|
||||
disable_form: function (frm) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# License: MIT. See LICENSE
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.modules.export_file import export_to_files
|
||||
|
||||
|
|
@ -37,6 +38,16 @@ class ModuleOnboarding(Document):
|
|||
|
||||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def reset_progress(self):
|
||||
self.db_set("is_complete", 0)
|
||||
|
||||
for step in self.get_steps():
|
||||
step.db_set("is_complete", 0)
|
||||
step.db_set("is_skipped", 0)
|
||||
|
||||
frappe.msgprint(_("Module onboarding progress reset"), alert=True)
|
||||
|
||||
def before_export(self, doc):
|
||||
doc.is_complete = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@
|
|||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "For User",
|
||||
"options": "User"
|
||||
"options": "User",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "type",
|
||||
|
|
@ -64,8 +65,7 @@
|
|||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "From User",
|
||||
"options": "User",
|
||||
"search_index": 1
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -96,7 +96,7 @@
|
|||
"hide_toolbar": 1,
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-13 16:08:48.153934",
|
||||
"modified": "2023-06-14 21:20:51.197943",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Notification Log",
|
||||
|
|
|
|||
|
|
@ -81,13 +81,21 @@ class ToDo(Document):
|
|||
)
|
||||
assignments.reverse()
|
||||
|
||||
frappe.db.set_value(
|
||||
self.reference_type,
|
||||
self.reference_name,
|
||||
"_assign",
|
||||
json.dumps(assignments),
|
||||
update_modified=False,
|
||||
)
|
||||
if frappe.get_meta(self.reference_type).issingle:
|
||||
frappe.db.set_single_value(
|
||||
self.reference_type,
|
||||
"_assign",
|
||||
json.dumps(assignments),
|
||||
update_modified=False,
|
||||
)
|
||||
else:
|
||||
frappe.db.set_value(
|
||||
self.reference_type,
|
||||
self.reference_name,
|
||||
"_assign",
|
||||
json.dumps(assignments),
|
||||
update_modified=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
if frappe.db.is_table_missing(e) and frappe.flags.in_install:
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@
|
|||
],
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-17 14:52:38.110224",
|
||||
"modified": "2023-06-08 14:52:38.110224",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Workspace",
|
||||
|
|
|
|||
|
|
@ -531,13 +531,13 @@ def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
|
|||
{"Address": {"fieldname": "customer"}..}
|
||||
"""
|
||||
if without_ignore_user_permissions_enabled:
|
||||
return frappe.cache().hget(
|
||||
return frappe.cache.hget(
|
||||
"linked_doctypes_without_ignore_user_permissions_enabled",
|
||||
doctype,
|
||||
lambda: _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled),
|
||||
)
|
||||
else:
|
||||
return frappe.cache().hget("linked_doctypes", doctype, lambda: _get_linked_doctypes(doctype))
|
||||
return frappe.cache.hget("linked_doctypes", doctype, lambda: _get_linked_doctypes(doctype))
|
||||
|
||||
|
||||
def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ def get_meta_bundle(doctype):
|
|||
bundle = [frappe.desk.form.meta.get_meta(doctype)]
|
||||
for df in bundle[0].fields:
|
||||
if df.fieldtype in frappe.model.table_fields:
|
||||
bundle.append(frappe.desk.form.meta.get_meta(df.options, not frappe.conf.developer_mode))
|
||||
bundle.append(frappe.desk.form.meta.get_meta(df.options))
|
||||
return bundle
|
||||
|
||||
|
||||
|
|
@ -202,11 +202,13 @@ def get_versions(doc):
|
|||
|
||||
@frappe.whitelist()
|
||||
def get_communications(doctype, name, start=0, limit=20):
|
||||
from frappe.utils import cint
|
||||
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
if not doc.has_permission("read"):
|
||||
raise frappe.PermissionError
|
||||
|
||||
return _get_communications(doctype, name, start, limit)
|
||||
return _get_communications(doctype, name, cint(start), cint(limit))
|
||||
|
||||
|
||||
def get_comments(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from frappe.build import scrub_html_template
|
|||
from frappe.model.meta import Meta
|
||||
from frappe.model.utils import render_include
|
||||
from frappe.modules import get_module_path, load_doctype_module, scrub
|
||||
from frappe.translate import extract_messages_from_code, make_dict_from_messages
|
||||
from frappe.utils import get_html_format
|
||||
from frappe.utils.data import get_link_to_form
|
||||
|
||||
|
|
@ -34,13 +33,15 @@ ASSET_KEYS = (
|
|||
)
|
||||
|
||||
|
||||
def get_meta(doctype, cached=True):
|
||||
def get_meta(doctype, cached=True) -> "FormMeta":
|
||||
# don't cache for developer mode as js files, templates may be edited
|
||||
if cached and not frappe.conf.developer_mode:
|
||||
meta = frappe.cache().hget("doctype_form_meta", doctype)
|
||||
cached = cached and not frappe.conf.developer_mode
|
||||
if cached:
|
||||
meta = frappe.cache.hget("doctype_form_meta", doctype)
|
||||
if not meta:
|
||||
meta = FormMeta(doctype)
|
||||
frappe.cache().hset("doctype_form_meta", doctype, meta)
|
||||
# Cache miss - explicitly get meta from DB to avoid
|
||||
meta = FormMeta(doctype, cached=False)
|
||||
frappe.cache.hset("doctype_form_meta", doctype, meta)
|
||||
else:
|
||||
meta = FormMeta(doctype)
|
||||
|
||||
|
|
@ -51,8 +52,8 @@ def get_meta(doctype, cached=True):
|
|||
|
||||
|
||||
class FormMeta(Meta):
|
||||
def __init__(self, doctype):
|
||||
self.__dict__.update(frappe.get_meta(doctype).__dict__)
|
||||
def __init__(self, doctype, *, cached=True):
|
||||
self.__dict__.update(frappe.get_meta(doctype, cached=cached).__dict__)
|
||||
self.load_assets()
|
||||
|
||||
def load_assets(self):
|
||||
|
|
@ -258,6 +259,8 @@ class FormMeta(Meta):
|
|||
self.set("__form_grid_templates", templates)
|
||||
|
||||
def set_translations(self, lang):
|
||||
from frappe.translate import extract_messages_from_code, make_dict_from_messages
|
||||
|
||||
self.set("__messages", frappe.get_lang_dict("doctype", self.name))
|
||||
|
||||
# set translations for grid templates
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from frappe.utils.telemetry import capture_doc
|
|||
def savedocs(doc, action):
|
||||
"""save / submit / update doclist"""
|
||||
doc = frappe.get_doc(json.loads(doc))
|
||||
capture_doc(doc)
|
||||
capture_doc(doc, action)
|
||||
set_local_name(doc)
|
||||
|
||||
# action
|
||||
|
|
@ -47,6 +47,8 @@ def savedocs(doc, action):
|
|||
def cancel(doctype=None, name=None, workflow_state_fieldname=None, workflow_state=None):
|
||||
"""cancel a doclist"""
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
capture_doc(doc, "Cancel")
|
||||
|
||||
if workflow_state_fieldname and workflow_state:
|
||||
doc.set(workflow_state_fieldname, workflow_state)
|
||||
doc.cancel()
|
||||
|
|
|
|||
|
|
@ -52,7 +52,10 @@ def _toggle_like(doctype, name, add, user=None):
|
|||
liked_by.remove(user)
|
||||
remove_like(doctype, name)
|
||||
|
||||
frappe.db.set_value(doctype, name, "_liked_by", json.dumps(liked_by), update_modified=False)
|
||||
if frappe.get_meta(doctype).issingle:
|
||||
frappe.db.set_single_value(doctype, "_liked_by", json.dumps(liked_by), update_modified=False)
|
||||
else:
|
||||
frappe.db.set_value(doctype, name, "_liked_by", json.dumps(liked_by), update_modified=False)
|
||||
|
||||
except frappe.db.ProgrammingError as e:
|
||||
if frappe.db.is_column_missing(e):
|
||||
|
|
|
|||
|
|
@ -34,13 +34,12 @@ def get_notifications():
|
|||
return out
|
||||
|
||||
groups = list(config.get("for_doctype")) + list(config.get("for_module"))
|
||||
cache = frappe.cache()
|
||||
|
||||
notification_count = {}
|
||||
notification_percent = {}
|
||||
|
||||
for name in groups:
|
||||
count = cache.hget("notification_count:" + name, frappe.session.user)
|
||||
count = frappe.cache.hget("notification_count:" + name, frappe.session.user)
|
||||
if count is not None:
|
||||
notification_count[name] = count
|
||||
|
||||
|
|
@ -83,7 +82,7 @@ def get_notifications_for_doctypes(config, notification_count):
|
|||
|
||||
else:
|
||||
open_count_doctype[d] = result
|
||||
frappe.cache().hset("notification_count:" + d, frappe.session.user, result)
|
||||
frappe.cache.hset("notification_count:" + d, frappe.session.user, result)
|
||||
|
||||
return open_count_doctype
|
||||
|
||||
|
|
@ -139,7 +138,6 @@ def get_notifications_for_targets(config, notification_percent):
|
|||
def clear_notifications(user=None):
|
||||
if frappe.flags.in_install:
|
||||
return
|
||||
cache = frappe.cache()
|
||||
config = get_notification_config()
|
||||
|
||||
if not config:
|
||||
|
|
@ -151,17 +149,17 @@ def clear_notifications(user=None):
|
|||
|
||||
for name in groups:
|
||||
if user:
|
||||
cache.hdel("notification_count:" + name, user)
|
||||
frappe.cache.hdel("notification_count:" + name, user)
|
||||
else:
|
||||
cache.delete_key("notification_count:" + name)
|
||||
frappe.cache.delete_key("notification_count:" + name)
|
||||
|
||||
|
||||
def clear_notification_config(user):
|
||||
frappe.cache().hdel("notification_config", user)
|
||||
frappe.cache.hdel("notification_config", user)
|
||||
|
||||
|
||||
def delete_notification_count_for(doctype):
|
||||
frappe.cache().delete_key("notification_count:" + doctype)
|
||||
frappe.cache.delete_key("notification_count:" + doctype)
|
||||
|
||||
|
||||
def clear_doctype_notifications(doc, method=None, *args, **kwargs):
|
||||
|
|
@ -230,7 +228,7 @@ def get_notification_config():
|
|||
config[key].update(nc.get(key, {}))
|
||||
return config
|
||||
|
||||
return frappe.cache().hget("notification_config", user, _get)
|
||||
return frappe.cache.hget("notification_config", user, _get)
|
||||
|
||||
|
||||
def get_filters_for(doctype):
|
||||
|
|
|
|||
|
|
@ -325,8 +325,8 @@ def load_country():
|
|||
@frappe.whitelist()
|
||||
def load_user_details():
|
||||
return {
|
||||
"full_name": frappe.cache().hget("full_name", "signup"),
|
||||
"email": frappe.cache().hget("email", "signup"),
|
||||
"full_name": frappe.cache.hget("full_name", "signup"),
|
||||
"email": frappe.cache.hget("email", "signup"),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ def generate_report_result(
|
|||
"report_summary": report_summary,
|
||||
"skip_total_row": skip_total_row or 0,
|
||||
"status": None,
|
||||
"execution_time": frappe.cache().hget("report_execution_time", report.name) or 0,
|
||||
"execution_time": frappe.cache.hget("report_execution_time", report.name) or 0,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -170,7 +170,8 @@ def get_script(report_name):
|
|||
return {
|
||||
"script": render_include(script),
|
||||
"html_format": html_format,
|
||||
"execution_time": frappe.cache().hget("report_execution_time", report_name) or 0,
|
||||
"execution_time": frappe.cache.hget("report_execution_time", report_name) or 0,
|
||||
"filters": report.filters,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -348,6 +349,13 @@ def build_xlsx_data(data, visible_idx, include_indentation, ignore_visible_idx=F
|
|||
datetime.timedelta,
|
||||
)
|
||||
|
||||
if len(visible_idx) == len(data.result):
|
||||
# It's not possible to have same length and different content.
|
||||
ignore_visible_idx = True
|
||||
else:
|
||||
# Note: converted for faster lookups
|
||||
visible_idx = set(visible_idx)
|
||||
|
||||
result = [[]]
|
||||
column_widths = []
|
||||
|
||||
|
|
|
|||
|
|
@ -479,6 +479,7 @@ def delete_items():
|
|||
|
||||
|
||||
def delete_bulk(doctype, items):
|
||||
undeleted_items = []
|
||||
for i, d in enumerate(items):
|
||||
try:
|
||||
frappe.delete_doc(doctype, d)
|
||||
|
|
@ -493,7 +494,11 @@ def delete_bulk(doctype, items):
|
|||
except Exception:
|
||||
# rollback if any record failed to delete
|
||||
# if not rollbacked, queries get committed on after_request method in app.py
|
||||
undeleted_items.append(d)
|
||||
frappe.db.rollback()
|
||||
if undeleted_items and len(items) != len(undeleted_items):
|
||||
frappe.clear_messages()
|
||||
delete_bulk(doctype, undeleted_items)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import json
|
|||
import re
|
||||
|
||||
import frappe
|
||||
from frappe import _, is_whitelisted
|
||||
|
||||
# Backward compatbility
|
||||
from frappe import _, is_whitelisted, validate_and_sanitize_search_inputs
|
||||
from frappe.database.schema import SPECIAL_CHAR_PATTERN
|
||||
from frappe.permissions import has_permission
|
||||
from frappe.utils import cint, cstr, unique
|
||||
|
|
@ -293,26 +295,10 @@ def relevance_sorter(key, query, as_dict):
|
|||
return (cstr(value).casefold().startswith(query.casefold()) is not True, value)
|
||||
|
||||
|
||||
def validate_and_sanitize_search_inputs(fn):
|
||||
@functools.wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
kwargs.update(dict(zip(fn.__code__.co_varnames, args)))
|
||||
sanitize_searchfield(kwargs["searchfield"])
|
||||
kwargs["start"] = cint(kwargs["start"])
|
||||
kwargs["page_len"] = cint(kwargs["page_len"])
|
||||
|
||||
if kwargs["doctype"] and not frappe.db.exists("DocType", kwargs["doctype"]):
|
||||
return []
|
||||
|
||||
return fn(**kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_names_for_mentions(search_term):
|
||||
users_for_mentions = frappe.cache().get_value("users_for_mentions", get_users_for_mentions)
|
||||
user_groups = frappe.cache().get_value("user_groups", get_user_groups)
|
||||
users_for_mentions = frappe.cache.get_value("users_for_mentions", get_users_for_mentions)
|
||||
user_groups = frappe.cache.get_value("user_groups", get_user_groups)
|
||||
|
||||
filtered_mentions = []
|
||||
for mention_data in users_for_mentions + user_groups:
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ def get_communication_doctype(doctype, txt, searchfield, start, page_len, filter
|
|||
|
||||
|
||||
def get_cached_contacts(txt):
|
||||
contacts = frappe.cache().hget("contacts", frappe.session.user) or []
|
||||
contacts = frappe.cache.hget("contacts", frappe.session.user) or []
|
||||
|
||||
if not contacts:
|
||||
return
|
||||
|
|
@ -113,9 +113,9 @@ def get_cached_contacts(txt):
|
|||
|
||||
|
||||
def update_contact_cache(contacts):
|
||||
cached_contacts = frappe.cache().hget("contacts", frappe.session.user) or []
|
||||
cached_contacts = frappe.cache.hget("contacts", frappe.session.user) or []
|
||||
|
||||
uncached_contacts = [d for d in contacts if d not in cached_contacts]
|
||||
cached_contacts.extend(uncached_contacts)
|
||||
|
||||
frappe.cache().hset("contacts", frappe.session.user, cached_contacts)
|
||||
frappe.cache.hset("contacts", frappe.session.user, cached_contacts)
|
||||
|
|
|
|||
|
|
@ -508,7 +508,7 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.domain && doc.enable_outgoing",
|
||||
"depends_on": "eval:!doc.domain && doc.enable_outgoing && doc.enable_incoming && doc.use_imap",
|
||||
"fieldname": "append_emails_to_sent_folder",
|
||||
"fieldtype": "Check",
|
||||
"hide_days": 1,
|
||||
|
|
@ -616,7 +616,7 @@
|
|||
"icon": "fa fa-inbox",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-12-28 14:56:18.754804",
|
||||
"modified": "2023-06-05 15:03:08.538819",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Account",
|
||||
|
|
@ -639,4 +639,4 @@
|
|||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ class EmailAccount(Document):
|
|||
|
||||
def get_incoming_server(self, in_receive=False, email_sync_rule="UNSEEN"):
|
||||
"""Returns logged in POP3/IMAP connection object."""
|
||||
if frappe.cache().get_value("workers:no-internet") == True:
|
||||
if frappe.cache.get_value("workers:no-internet") == True:
|
||||
return None
|
||||
|
||||
oauth_token = self.get_oauth_token()
|
||||
|
|
@ -253,7 +253,7 @@ class EmailAccount(Document):
|
|||
if self.no_failed > 2:
|
||||
self.handle_incoming_connect_error(description=description)
|
||||
else:
|
||||
frappe.cache().set_value("workers:no-internet", True)
|
||||
frappe.cache.set_value("workers:no-internet", True)
|
||||
return None
|
||||
else:
|
||||
raise
|
||||
|
|
@ -384,6 +384,10 @@ class EmailAccount(Document):
|
|||
"name": {"conf_names": ("email_sender_name",), "default": "Frappe"},
|
||||
"auth_method": {"conf_names": ("auth_method"), "default": "Basic"},
|
||||
"from_site_config": {"default": True},
|
||||
"no_smtp_authentication": {
|
||||
"conf_names": ("disable_mail_smtp_authentication",),
|
||||
"default": 0,
|
||||
},
|
||||
}
|
||||
|
||||
account_details = {}
|
||||
|
|
@ -436,13 +440,13 @@ class EmailAccount(Document):
|
|||
else:
|
||||
self.set_failed_attempts_count(self.get_failed_attempts_count() + 1)
|
||||
else:
|
||||
frappe.cache().set_value("workers:no-internet", True)
|
||||
frappe.cache.set_value("workers:no-internet", True)
|
||||
|
||||
def set_failed_attempts_count(self, value):
|
||||
frappe.cache().set(f"{self.name}:email-account-failed-attempts", value)
|
||||
frappe.cache.set(f"{self.name}:email-account-failed-attempts", value)
|
||||
|
||||
def get_failed_attempts_count(self):
|
||||
return cint(frappe.cache().get(f"{self.name}:email-account-failed-attempts"))
|
||||
return cint(frappe.cache.get(f"{self.name}:email-account-failed-attempts"))
|
||||
|
||||
def receive(self):
|
||||
"""Called by scheduler to receive emails from this EMail account using POP3/IMAP."""
|
||||
|
|
@ -648,21 +652,16 @@ class EmailAccount(Document):
|
|||
frappe.throw(_("Automatic Linking can be activated only for one Email Account."))
|
||||
|
||||
def append_email_to_sent_folder(self, message):
|
||||
email_server = None
|
||||
try:
|
||||
email_server = self.get_incoming_server(in_receive=True)
|
||||
except Exception:
|
||||
self.log_error("Email Connection Error")
|
||||
|
||||
if not email_server:
|
||||
if not (self.enable_incoming and self.use_imap):
|
||||
# don't try appending if enable incoming and imap is not set
|
||||
return
|
||||
|
||||
if email_server.imap:
|
||||
try:
|
||||
message = safe_encode(message)
|
||||
email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message)
|
||||
except Exception:
|
||||
self.log_error("Unable to add to Sent folder")
|
||||
try:
|
||||
email_server = self.get_incoming_server(in_receive=True)
|
||||
message = safe_encode(message)
|
||||
email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message)
|
||||
except Exception:
|
||||
self.log_error("Unable to add to Sent folder")
|
||||
|
||||
def get_oauth_token(self):
|
||||
if self.auth_method == "OAuth":
|
||||
|
|
@ -766,9 +765,9 @@ def pull(now=False):
|
|||
"""Will be called via scheduler, pull emails from all enabled Email accounts."""
|
||||
from frappe.integrations.doctype.connected_app.connected_app import has_token
|
||||
|
||||
if frappe.cache().get_value("workers:no-internet") == True:
|
||||
if frappe.cache.get_value("workers:no-internet") == True:
|
||||
if test_internet():
|
||||
frappe.cache().set_value("workers:no-internet", False)
|
||||
frappe.cache.set_value("workers:no-internet", False)
|
||||
return
|
||||
|
||||
doctype = frappe.qb.DocType("Email Account")
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue