chore: weekly version-14 releaseversion-14
@@ -17,39 +17,57 @@ def fetch_pr_data(pr_number, repo, endpoint=""): | |||
req = urllib.request.Request(api_url) | |||
res = urllib.request.urlopen(req) | |||
return json.loads(res.read().decode('utf8')) | |||
return json.loads(res.read().decode("utf8")) | |||
def get_files_list(pr_number, repo="frappe/frappe"): | |||
return [change["filename"] for change in fetch_pr_data(pr_number, repo, "files")] | |||
def get_output(command, shell=True): | |||
print(command) | |||
command = shlex.split(command) | |||
return subprocess.check_output(command, shell=shell, encoding="utf8").strip() | |||
def has_skip_ci_label(pr_number, repo="frappe/frappe"): | |||
return has_label(pr_number, "Skip CI", repo) | |||
def has_run_server_tests_label(pr_number, repo="frappe/frappe"): | |||
return has_label(pr_number, "Run Server Tests", repo) | |||
def has_run_ui_tests_label(pr_number, repo="frappe/frappe"): | |||
return has_label(pr_number, "Run UI Tests", repo) | |||
def has_label(pr_number, label, repo="frappe/frappe"): | |||
return any([fetched_label["name"] for fetched_label in fetch_pr_data(pr_number, repo)["labels"] if fetched_label["name"] == label]) | |||
return any( | |||
[ | |||
fetched_label["name"] | |||
for fetched_label in fetch_pr_data(pr_number, repo)["labels"] | |||
if fetched_label["name"] == label | |||
] | |||
) | |||
def is_py(file): | |||
return file.endswith("py") | |||
def is_ci(file): | |||
return ".github" in file | |||
def is_frontend_code(file): | |||
return file.lower().endswith((".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html")) | |||
return file.lower().endswith( | |||
(".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html") | |||
) | |||
def is_docs(file): | |||
regex = re.compile(r'\.(md|png|jpg|jpeg|csv|svg)$|^.github|LICENSE') | |||
regex = re.compile(r"\.(md|png|jpg|jpeg|csv|svg)$|^.github|LICENSE") | |||
return bool(regex.search(file)) | |||
@@ -78,8 +96,13 @@ if __name__ == "__main__": | |||
only_py_changed = updated_py_file_count == len(files_list) | |||
if has_skip_ci_label(pr_number, repo): | |||
print("Found `Skip CI` label on pr, stopping build process.") | |||
sys.exit(0) | |||
if build_type == "ui" and has_run_ui_tests_label(pr_number, repo): | |||
print("Running UI tests only.") | |||
elif build_type == "server" and has_run_server_tests_label(pr_number, repo): | |||
print("Running server tests only.") | |||
else: | |||
print("Found `Skip CI` label on pr, stopping build process.") | |||
sys.exit(0) | |||
elif ci_files_changed: | |||
print("CI related files were updated, running all build processes.") | |||
@@ -88,7 +111,11 @@ if __name__ == "__main__": | |||
print("Only docs were updated, stopping build process.") | |||
sys.exit(0) | |||
elif only_frontend_code_changed and build_type == "server" and not has_run_server_tests_label(pr_number, repo): | |||
elif ( | |||
only_frontend_code_changed | |||
and build_type == "server" | |||
and not has_run_server_tests_label(pr_number, repo) | |||
): | |||
print("Only Frontend code was updated; Stopping Python build process.") | |||
sys.exit(0) | |||
@@ -22,8 +22,6 @@ jobs: | |||
strategy: | |||
fail-fast: false | |||
matrix: | |||
container: [1, 2] | |||
services: | |||
mariadb: | |||
@@ -122,7 +120,4 @@ jobs: | |||
- name: Run Tests | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator | |||
env: | |||
CI_BUILD_ID: ${{ github.run_id }} | |||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | |||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests |
@@ -21,8 +21,6 @@ jobs: | |||
strategy: | |||
fail-fast: false | |||
matrix: | |||
container: [1, 2] | |||
services: | |||
postgres: | |||
@@ -125,7 +123,4 @@ jobs: | |||
- name: Run Tests | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator | |||
env: | |||
CI_BUILD_ID: ${{ github.run_id }} | |||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | |||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests |
@@ -21,7 +21,7 @@ jobs: | |||
strategy: | |||
fail-fast: false | |||
matrix: | |||
containers: [1, 2, 3] | |||
containers: [1, 2] | |||
name: UI Tests (Cypress) | |||
@@ -105,19 +105,16 @@ jobs: | |||
id: yarn-cache | |||
with: | |||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | |||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} | |||
key: ${{ runner.os }}-yarn-ui-${{ hashFiles('**/yarn.lock') }} | |||
restore-keys: | | |||
${{ runner.os }}-yarn- | |||
${{ runner.os }}-yarn-ui- | |||
- name: Cache cypress binary | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
uses: actions/cache@v3 | |||
with: | |||
path: ~/.cache | |||
key: ${{ runner.os }}-cypress- | |||
restore-keys: | | |||
${{ runner.os }}-cypress- | |||
${{ runner.os }}- | |||
path: ~/.cache/Cypress | |||
key: ${{ runner.os }}-cypress | |||
- name: Install Dependencies | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
@@ -0,0 +1,23 @@ | |||
const { defineConfig } = require("cypress"); | |||
module.exports = defineConfig({ | |||
projectId: "92odwv", | |||
adminPassword: "admin", | |||
defaultCommandTimeout: 20000, | |||
pageLoadTimeout: 15000, | |||
video: true, | |||
videoUploadOnPasses: false, | |||
retries: { | |||
runMode: 2, | |||
openMode: 2, | |||
}, | |||
e2e: { | |||
// We've imported your old cypress plugins here. | |||
// You may want to clean this up later by importing these. | |||
setupNodeEvents(on, config) { | |||
return require("./cypress/plugins/index.js")(on, config); | |||
}, | |||
baseUrl: "http://test_site_ui:8000", | |||
specPattern: ["./cypress/integration/*.js", "**/ui_test_*.js"], | |||
}, | |||
}); |
@@ -1,15 +0,0 @@ | |||
{ | |||
"baseUrl": "http://test_site_ui:8000", | |||
"projectId": "92odwv", | |||
"adminPassword": "admin", | |||
"defaultCommandTimeout": 20000, | |||
"pageLoadTimeout": 15000, | |||
"video": true, | |||
"videoUploadOnPasses": false, | |||
"retries": { | |||
"runMode": 2, | |||
"openMode": 2 | |||
}, | |||
"integrationFolder": ".", | |||
"testFiles": ["cypress/integration/*.js", "**/ui_test_*.js"] | |||
} |
@@ -29,6 +29,7 @@ context("Control Float", () => { | |||
}); | |||
x.values.forEach((d) => { | |||
cy.get_field("float_number", "Float").clear(); | |||
cy.wait(200); | |||
cy.fill_field("float_number", d.input, "Float").blur(); | |||
cy.get_field("float_number", "Float").should("have.value", d.blur_expected); | |||
@@ -132,9 +132,6 @@ context("Form", () => { | |||
jump_to_field("Username"); | |||
type_value("admin42"); | |||
jump_to_field("Birth Date"); | |||
type_value("12-31-01"); | |||
jump_to_field("Send Welcome Email"); | |||
cy.focused().uncheck(); | |||
@@ -155,16 +152,15 @@ context("Form", () => { | |||
undo(); | |||
undo(); | |||
undo(); | |||
undo(); | |||
redo(); | |||
redo(); | |||
redo(); | |||
redo(); | |||
redo(); | |||
cy.get_field("username").should("have.value", "admin24"); | |||
cy.get_field("email").should("have.value", "admin@example.com"); | |||
cy.get_field("birth_date").should("have.value", "12-31-2001"); // parsed value | |||
cy.get_field("send_welcome_email").should("not.be.checked"); | |||
cy.compare_document({ | |||
username: "admin24", | |||
email: "admin@example.com", | |||
send_welcome_email: 0, | |||
}); | |||
}); | |||
}); |
@@ -96,17 +96,4 @@ context("Kanban Board", () => { | |||
.first() | |||
.should("not.contain", "ID:"); | |||
}); | |||
// it('Drag todo', () => { | |||
// cy.intercept({ | |||
// method: 'POST', | |||
// url: 'api/method/frappe.desk.doctype.kanban_board.kanban_board.update_order_for_single_card' | |||
// }).as('drag-completed'); | |||
// cy.get('.kanban-card-body') | |||
// .contains('Test Kanban ToDo').first() | |||
// .drag('[data-column-value="Closed"] .kanban-cards', { force: true }); | |||
// cy.wait('@drag-completed'); | |||
// }); | |||
}); |
@@ -10,10 +10,13 @@ context("List View", () => { | |||
}); | |||
}); | |||
it("Keep checkbox checked after Refresh", () => { | |||
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(".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 | |||
@@ -22,7 +25,7 @@ context("List View", () => { | |||
cy.get(".list-row-container .list-row-checkbox:checked").should("be.visible"); | |||
}); | |||
it('enables "Actions" button', () => { | |||
it('enables "Actions" button', { scrollBehavior: false }, () => { | |||
const actions = [ | |||
"Approve", | |||
"Reject", | |||
@@ -5,12 +5,14 @@ context("List View Settings", () => { | |||
}); | |||
it("Default settings", () => { | |||
cy.visit("/app/List/DocType/List"); | |||
cy.clear_filters(); | |||
cy.get(".list-count").should("contain", "20 of"); | |||
cy.get(".list-stats").should("contain", "Tags"); | |||
}); | |||
it("disable count and sidebar stats then verify", () => { | |||
cy.wait(300); | |||
cy.visit("/app/List/DocType/List"); | |||
cy.clear_filters(); | |||
cy.wait(300); | |||
cy.get(".list-count").should("contain", "20 of"); | |||
cy.get(".menu-btn-group button").click(); | |||
@@ -1,93 +0,0 @@ | |||
context("Timeline Email", () => { | |||
before(() => { | |||
cy.visit("/login"); | |||
cy.login(); | |||
cy.visit("/app/todo"); | |||
}); | |||
it("Adding new ToDo", () => { | |||
cy.click_listview_primary_button("Add ToDo"); | |||
cy.get(".custom-actions:visible > .btn").contains("Edit Full Form").click({ delay: 500 }); | |||
cy.fill_field("description", "Test ToDo", "Text Editor"); | |||
cy.wait(500); | |||
cy.get(".primary-action").contains("Save").click({ force: true }); | |||
cy.wait(700); | |||
}); | |||
it("Adding email and verifying timeline content for email attachment", () => { | |||
cy.visit("/app/todo"); | |||
cy.click_listview_row_item_with_text("Test ToDo"); | |||
//Creating a new email | |||
cy.get(".timeline-actions > .timeline-item > .action-buttons > .action-btn").click(); | |||
cy.fill_field("recipients", "test@example.com", "MultiSelect"); | |||
cy.get( | |||
'.modal.show > .modal-dialog > .modal-content > .modal-body > :nth-child(1) > .form-layout > .form-page > :nth-child(3) > .section-body > .form-column > form > [data-fieldtype="Text Editor"] > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor' | |||
).type("Test Mail"); | |||
//Adding attachment to the email | |||
cy.get(".add-more-attachments > .btn").click(); | |||
cy.get(".mt-2 > .btn > .mt-1").eq(2).click(); | |||
cy.get(".input-group > .form-control").type( | |||
"https://wallpaperplay.com/walls/full/8/2/b/72402.jpg" | |||
); | |||
cy.get(".btn-primary").contains("Upload").click(); | |||
//Sending the email | |||
cy.click_modal_primary_button("Send", { delay: 500 }); | |||
//To check if the sent mail content is shown in the timeline content | |||
cy.get('[data-doctype="Communication"] > .timeline-content').should( | |||
"contain", | |||
"Test Mail" | |||
); | |||
//To check if the attachment of email is shown in the timeline content | |||
cy.get(".timeline-content").should("contain", "Added 72402.jpg"); | |||
//Deleting the sent email | |||
cy.get('[title="Open Communication"] > .icon').first().click({ force: true }); | |||
cy.get( | |||
"#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn" | |||
).click(); | |||
cy.get( | |||
"#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .dropdown-menu > li > .grey-link" | |||
) | |||
.eq(9) | |||
.click(); | |||
cy.get( | |||
".modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary" | |||
).click(); | |||
}); | |||
it("Deleting attachment and ToDo", () => { | |||
cy.visit("/app/todo"); | |||
cy.click_listview_row_item_with_text("Test ToDo"); | |||
//Removing the added attachment | |||
cy.get(".attachment-row > .data-pill > .remove-btn > .icon").click(); | |||
cy.wait(500); | |||
cy.get(".modal-footer:visible > .standard-actions > .btn-primary").contains("Yes").click(); | |||
//To check if the removed attachment is shown in the timeline content | |||
cy.get(".timeline-content").should("contain", "Removed 72402.jpg"); | |||
cy.wait(500); | |||
//To check if the discard button functionality in email is working correctly | |||
cy.get(".timeline-actions > .timeline-item > .action-buttons > .action-btn").click(); | |||
cy.fill_field("recipients", "test@example.com", "MultiSelect"); | |||
cy.get(".modal-footer > .standard-actions > .btn-secondary").contains("Discard").click(); | |||
cy.wait(500); | |||
cy.get(".timeline-actions > .timeline-item > .action-buttons > .action-btn").click(); | |||
cy.wait(500); | |||
cy.get_field("recipients", "MultiSelect").should("have.text", ""); | |||
cy.get(".modal-header:visible > .modal-actions > .btn-modal-close > .icon").click(); | |||
//Deleting the added ToDo | |||
cy.get(".menu-btn-group:visible > .btn").click(); | |||
cy.get(".menu-btn-group:visible > .dropdown-menu > li > .dropdown-item") | |||
.contains("Delete") | |||
.click(); | |||
cy.get(".modal-footer:visible > .standard-actions > .btn-primary").click(); | |||
}); | |||
}); |
@@ -68,9 +68,10 @@ context("Workspace Blocks", () => { | |||
cy.intercept({ | |||
method: "GET", | |||
url: "api/method/frappe.desk.form.load.getdoctype", | |||
url: "api/method/frappe.desk.form.load.getdoctype?**", | |||
}).as("get_doctype"); | |||
cy.visit("/app/tools"); | |||
cy.get(".codex-editor__redactor .ce-block"); | |||
cy.get(".standard-actions .btn-secondary[data-label=Edit]").click(); | |||
@@ -281,12 +281,12 @@ Cypress.Commands.add("get_open_dialog", () => { | |||
}); | |||
Cypress.Commands.add("save", () => { | |||
cy.intercept("/api").as("api"); | |||
cy.intercept("/api/method/frappe.desk.form.save.savedocs").as("save_call"); | |||
cy.get(`button[data-label="Save"]:visible`).click({ scrollBehavior: false, force: true }); | |||
cy.wait("@api"); | |||
cy.wait("@save_call"); | |||
}); | |||
Cypress.Commands.add("hide_dialog", () => { | |||
cy.wait(300); | |||
cy.wait(500); | |||
cy.get_open_dialog().focus().find(".btn-modal-close").click(); | |||
cy.get(".modal:visible").should("not.exist"); | |||
}); | |||
@@ -459,3 +459,26 @@ Cypress.Commands.add("select_listview_row_checkbox", (row_no) => { | |||
Cypress.Commands.add("click_form_section", (section_name) => { | |||
cy.get(".section-head").contains(section_name).click(); | |||
}); | |||
const compare_document = (expected, actual) => { | |||
for (const prop in expected) { | |||
if (expected[prop] instanceof Array) { | |||
// recursively compare child documents. | |||
expected[prop].forEach((item, idx) => { | |||
compare_document(item, actual[prop][idx]); | |||
}); | |||
} else { | |||
assert.equal(expected[prop], actual[prop], `${prop} should be equal.`); | |||
} | |||
} | |||
}; | |||
Cypress.Commands.add("compare_document", (expected_document) => { | |||
cy.window() | |||
.its("cur_frm") | |||
.then((frm) => { | |||
// Don't remove this, cypress can't magically wait for events it has no control over. | |||
cy.wait(1000); | |||
compare_document(expected_document, frm.doc); | |||
}); | |||
}); |
@@ -111,15 +111,15 @@ function get_redis_subscriber(kind) { | |||
let retry_strategy; | |||
let { get_redis_subscriber: get_redis, get_conf } = require("../node_utils"); | |||
if (process.env.CI == 1 || get_conf().developer_mode == 1) { | |||
if (process.env.CI == 1 || get_conf().developer_mode == 0) { | |||
retry_strategy = () => {}; | |||
} else { | |||
retry_strategy = function (options) { | |||
// abort after 10 connection attempts | |||
if (options.attempt > 10) { | |||
// abort after 5 x 3 connection attempts ~= 3 seconds | |||
if (options.attempt > 4) { | |||
return undefined; | |||
} | |||
return Math.min(options.attempt * 100, 2000); | |||
return options.attempt * 100; | |||
}; | |||
} | |||
return get_redis(kind, { retry_strategy }); | |||
@@ -46,7 +46,7 @@ class RequestContext: | |||
@local_manager.middleware | |||
@Request.application | |||
def application(request): | |||
def application(request: Request): | |||
response = None | |||
try: | |||
@@ -140,6 +140,9 @@ class LoginManager: | |||
self.set_user_info() | |||
def login(self): | |||
if frappe.get_system_settings("disable_user_pass_login"): | |||
frappe.throw(_("Login with username and password is not allowed."), frappe.AuthenticationError) | |||
# clear cache | |||
frappe.clear_cache(user=frappe.form_dict.get("usr")) | |||
user, pwd = get_cached_user_pass() | |||
@@ -226,14 +229,16 @@ class LoginManager: | |||
def clear_active_sessions(self): | |||
"""Clear other sessions of the current user if `deny_multiple_sessions` is not set""" | |||
if frappe.session.user == "Guest": | |||
return | |||
if not ( | |||
cint(frappe.conf.get("deny_multiple_sessions")) | |||
or cint(frappe.db.get_system_setting("deny_multiple_sessions")) | |||
): | |||
return | |||
if frappe.session.user != "Guest": | |||
clear_sessions(frappe.session.user, keep_current=True) | |||
clear_sessions(frappe.session.user, keep_current=True) | |||
def authenticate(self, user: str = None, pwd: str = None): | |||
from frappe.core.doctype.user.user import User | |||
@@ -879,7 +879,7 @@ def run_ui_tests( | |||
click.secho("Installing Cypress...", fg="yellow") | |||
packages = " ".join( | |||
[ | |||
"cypress@^6", | |||
"cypress@^10", | |||
"cypress-file-upload@^5", | |||
"@4tw/cypress-drag-drop@^2", | |||
"cypress-real-events", | |||
@@ -161,12 +161,13 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments): | |||
else: | |||
raise ImplicitCommitError | |||
else: | |||
if not frappe.flags.in_patch: | |||
reference_doc = frappe.get_doc(reference_doctype, reference_name) | |||
if getattr(reference_doc, "route", None): | |||
clear_cache(reference_doc.route) | |||
if frappe.flags.in_patch: | |||
return | |||
# Clear route cache | |||
if route := frappe.get_cached_value(reference_doctype, reference_name, "route"): | |||
clear_cache(route) | |||
def update_comments_in_parent_after_request(): | |||
@@ -16,6 +16,7 @@ DEFAULT_LOGTYPES_RETENTION = { | |||
"Email Queue": 30, | |||
"Error Snapshot": 30, | |||
"Scheduled Job Log": 90, | |||
"Route History": 90, | |||
} | |||
@@ -39,6 +39,7 @@ | |||
"deny_multiple_sessions", | |||
"allow_login_using_mobile_number", | |||
"allow_login_using_user_name", | |||
"disable_user_pass_login", | |||
"allow_error_traceback", | |||
"strip_exif_metadata_from_uploaded_images", | |||
"allow_older_web_view_links", | |||
@@ -525,12 +526,19 @@ | |||
"fieldname": "email_retry_limit", | |||
"fieldtype": "Int", | |||
"label": "Email Retry Limit" | |||
}, | |||
{ | |||
"default": "0", | |||
"description": "Make sure to configure a Social Login Key before disabling to prevent lockout", | |||
"fieldname": "disable_user_pass_login", | |||
"fieldtype": "Check", | |||
"label": "Disable Username/Password Login" | |||
} | |||
], | |||
"icon": "fa fa-cog", | |||
"issingle": 1, | |||
"links": [], | |||
"modified": "2022-06-21 13:55:04.796152", | |||
"modified": "2022-09-06 03:16:59.090906", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "System Settings", | |||
@@ -43,6 +43,22 @@ class SystemSettings(Document): | |||
): | |||
frappe.flags.update_last_reset_password_date = True | |||
self.validate_user_pass_login() | |||
def validate_user_pass_login(self): | |||
if not self.disable_user_pass_login: | |||
return | |||
social_login_enabled = frappe.db.exists("Social Login Key", {"enable_social_login": 1}) | |||
ldap_enabled = frappe.db.get_single_value("LDAP Settings", "enabled") | |||
if not (social_login_enabled or ldap_enabled): | |||
frappe.throw( | |||
_( | |||
"Please enable atleast one Social Login Key or LDAP before disabling username/password based login." | |||
) | |||
) | |||
def on_update(self): | |||
self.set_defaults() | |||
@@ -1,6 +1,6 @@ | |||
{ | |||
"charts": [], | |||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Workspace\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Elements</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}}]", | |||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Workspace\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Elements</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"System Logs\",\"col\":4}}]", | |||
"creation": "2021-01-02 10:51:16.579957", | |||
"docstatus": 0, | |||
"doctype": "Workspace", | |||
@@ -13,7 +13,7 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Modules", | |||
"label": "Models", | |||
"link_count": 0, | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
@@ -23,9 +23,9 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Module Def", | |||
"label": "DocType", | |||
"link_count": 0, | |||
"link_to": "Module Def", | |||
"link_to": "DocType", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
@@ -34,9 +34,9 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Workspace", | |||
"label": "Workflow", | |||
"link_count": 0, | |||
"link_to": "Workspace", | |||
"link_to": "Workflow", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
@@ -45,9 +45,19 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Module Onboarding", | |||
"label": "Scripting", | |||
"link_count": 0, | |||
"link_to": "Module Onboarding", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
"type": "Card Break" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Server Script", | |||
"link_count": 0, | |||
"link_to": "Server Script", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
@@ -56,9 +66,9 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Block Module", | |||
"label": "Client Script", | |||
"link_count": 0, | |||
"link_to": "Block Module", | |||
"link_to": "Client Script", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
@@ -67,30 +77,56 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Models", | |||
"label": "Scheduled Job Type", | |||
"link_count": 0, | |||
"link_to": "Scheduled Job Type", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Packages", | |||
"link_count": 2, | |||
"onboard": 0, | |||
"type": "Card Break" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "DocType", | |||
"label": "Package", | |||
"link_count": 0, | |||
"link_to": "DocType", | |||
"link_to": "Package", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Workflow", | |||
"label": "Package Import", | |||
"link_count": 0, | |||
"link_to": "Workflow", | |||
"link_to": "Package Import", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Modules", | |||
"link_count": 3, | |||
"onboard": 0, | |||
"type": "Card Break" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Module Def", | |||
"link_count": 0, | |||
"link_to": "Module Def", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
@@ -99,11 +135,30 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Views", | |||
"label": "Module Onboarding", | |||
"link_count": 0, | |||
"link_to": "Module Onboarding", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Module Profile", | |||
"link_count": 0, | |||
"link_to": "Module Profile", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Views", | |||
"link_count": 4, | |||
"onboard": 0, | |||
"type": "Card Break" | |||
}, | |||
{ | |||
@@ -131,9 +186,9 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Workspace", | |||
"label": "Dashboard", | |||
"link_count": 0, | |||
"link_to": "Workspace", | |||
"link_to": "Dashboard", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
@@ -142,71 +197,67 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Dashboard", | |||
"label": "Workspace", | |||
"link_count": 0, | |||
"link_to": "Dashboard", | |||
"link_to": "Workspace", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Scripting", | |||
"link_count": 0, | |||
"link_type": "DocType", | |||
"label": "System Logs", | |||
"link_count": 6, | |||
"onboard": 0, | |||
"only_for": "", | |||
"type": "Card Break" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Server Script", | |||
"label": "Background Jobs", | |||
"link_count": 0, | |||
"link_to": "Server Script", | |||
"link_type": "DocType", | |||
"link_to": "background_jobs", | |||
"link_type": "Page", | |||
"onboard": 0, | |||
"only_for": "", | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Client Script", | |||
"label": "Scheduled Jobs Logs", | |||
"link_count": 0, | |||
"link_to": "Client Script", | |||
"link_to": "Scheduled Job Log", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Scheduled Job Type", | |||
"label": "Error Logs", | |||
"link_count": 0, | |||
"link_to": "Scheduled Job Type", | |||
"link_to": "Error Log", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"only_for": "", | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Packages", | |||
"link_count": 2, | |||
"label": "Error Snapshot", | |||
"link_count": 0, | |||
"link_to": "Error Snapshot", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Card Break" | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Package", | |||
"label": "Communication Logs", | |||
"link_count": 0, | |||
"link_to": "Package", | |||
"link_to": "Communication", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
@@ -214,21 +265,22 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Package Import", | |||
"label": "Activity Log", | |||
"link_count": 0, | |||
"link_to": "Package Import", | |||
"link_to": "Activity Log", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
} | |||
], | |||
"modified": "2022-01-13 17:26:02.736366", | |||
"modified": "2022-09-02 01:48:28.029135", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Build", | |||
"owner": "Administrator", | |||
"parent_page": "", | |||
"public": 1, | |||
"quick_lists": [], | |||
"restrict_to_domain": "", | |||
"roles": [], | |||
"sequence_id": 5.0, | |||
@@ -1,6 +1,6 @@ | |||
{ | |||
"charts": [], | |||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Settings</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Website Settings\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]", | |||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Settings</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Website Settings\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]", | |||
"creation": "2020-03-02 15:09:40.527211", | |||
"docstatus": 0, | |||
"doctype": "Workspace", | |||
@@ -224,7 +224,7 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Core", | |||
"label": "Printing", | |||
"link_count": 0, | |||
"onboard": 0, | |||
"type": "Card Break" | |||
@@ -233,10 +233,10 @@ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "System Settings", | |||
"label": "Print Format Builder", | |||
"link_count": 0, | |||
"link_to": "System Settings", | |||
"link_type": "DocType", | |||
"link_to": "print-format-builder", | |||
"link_type": "Page", | |||
"onboard": 0, | |||
"type": "Link" | |||
}, | |||
@@ -244,9 +244,9 @@ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Error Log", | |||
"label": "Print Settings", | |||
"link_count": 0, | |||
"link_to": "Error Log", | |||
"link_to": "Print Settings", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
@@ -255,9 +255,9 @@ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Error Snapshot", | |||
"label": "Print Format", | |||
"link_count": 0, | |||
"link_to": "Error Snapshot", | |||
"link_to": "Print Format", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
@@ -266,9 +266,9 @@ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Domain Settings", | |||
"label": "Print Style", | |||
"link_count": 0, | |||
"link_to": "Domain Settings", | |||
"link_to": "Print Style", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
@@ -276,7 +276,7 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Printing", | |||
"label": "Workflow", | |||
"link_count": 0, | |||
"onboard": 0, | |||
"type": "Card Break" | |||
@@ -285,20 +285,9 @@ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Print Format Builder", | |||
"link_count": 0, | |||
"link_to": "print-format-builder", | |||
"link_type": "Page", | |||
"onboard": 0, | |||
"type": "Link" | |||
}, | |||
{ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Print Settings", | |||
"label": "Workflow", | |||
"link_count": 0, | |||
"link_to": "Print Settings", | |||
"link_to": "Workflow", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
@@ -307,9 +296,9 @@ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Print Format", | |||
"label": "Workflow State", | |||
"link_count": 0, | |||
"link_to": "Print Format", | |||
"link_to": "Workflow State", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
@@ -318,9 +307,9 @@ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Print Style", | |||
"label": "Workflow Action", | |||
"link_count": 0, | |||
"link_to": "Print Style", | |||
"link_to": "Workflow Action", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
@@ -328,8 +317,8 @@ | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Workflow", | |||
"link_count": 0, | |||
"label": "Core", | |||
"link_count": 2, | |||
"onboard": 0, | |||
"type": "Card Break" | |||
}, | |||
@@ -337,20 +326,9 @@ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Workflow", | |||
"link_count": 0, | |||
"link_to": "Workflow", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
}, | |||
{ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Workflow State", | |||
"label": "System Settings", | |||
"link_count": 0, | |||
"link_to": "Workflow State", | |||
"link_to": "System Settings", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
@@ -359,21 +337,22 @@ | |||
"dependencies": "", | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Workflow Action", | |||
"label": "Domain Settings", | |||
"link_count": 0, | |||
"link_to": "Workflow Action", | |||
"link_to": "Domain Settings", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
} | |||
], | |||
"modified": "2022-01-13 17:49:59.586909", | |||
"modified": "2022-08-28 21:41:28.065190", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Settings", | |||
"owner": "Administrator", | |||
"parent_page": "", | |||
"public": 1, | |||
"quick_lists": [], | |||
"restrict_to_domain": "", | |||
"roles": [], | |||
"sequence_id": 29.0, | |||
@@ -1,6 +1,6 @@ | |||
{ | |||
"charts": [], | |||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Customization\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Custom Role\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Dashboards\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Form Customization\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other\",\"col\":4}}]", | |||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Customization\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Custom Role\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Dashboards\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Form Customization\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other\",\"col\":4}}]", | |||
"creation": "2020-03-02 15:15:03.839594", | |||
"docstatus": 0, | |||
"doctype": "Workspace", | |||
@@ -107,7 +107,7 @@ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Other", | |||
"link_count": 0, | |||
"link_count": 2, | |||
"onboard": 0, | |||
"type": "Card Break" | |||
}, | |||
@@ -121,15 +121,26 @@ | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
}, | |||
{ | |||
"hidden": 0, | |||
"is_query_report": 0, | |||
"label": "Navbar Settings", | |||
"link_count": 0, | |||
"link_to": "Navbar Settings", | |||
"link_type": "DocType", | |||
"onboard": 0, | |||
"type": "Link" | |||
} | |||
], | |||
"modified": "2022-01-13 17:28:08.345794", | |||
"modified": "2022-08-28 20:56:24.980719", | |||
"modified_by": "Administrator", | |||
"module": "Custom", | |||
"name": "Customization", | |||
"owner": "Administrator", | |||
"parent_page": "", | |||
"public": 1, | |||
"quick_lists": [], | |||
"restrict_to_domain": "", | |||
"roles": [], | |||
"sequence_id": 8.0, | |||
@@ -138,6 +138,7 @@ class Workspace(Document): | |||
def disable_saving_as_public(): | |||
return ( | |||
frappe.flags.in_install | |||
or frappe.flags.in_uninstall | |||
or frappe.flags.in_patch | |||
or frappe.flags.in_test | |||
or frappe.flags.in_fixtures | |||
@@ -196,6 +196,7 @@ frappe.patches.v14_0.log_settings_migration | |||
frappe.patches.v14_0.setup_likes_from_feedback | |||
frappe.patches.v14_0.update_webforms | |||
frappe.patches.v14_0.delete_payment_gateways | |||
frappe.patches.v14_0.event_streaming_deprecation_warning | |||
[post_model_sync] | |||
frappe.patches.v14_0.drop_data_import_legacy | |||
@@ -0,0 +1,9 @@ | |||
import click | |||
def execute(): | |||
click.secho( | |||
"Event Streaming is moved to a separate app in version 15.\n" | |||
"When upgrading to Frappe version-15, Please install the 'Event Streaming' app to continue using them: https://github.com/frappe/event_streaming", | |||
fg="yellow", | |||
) |
@@ -2,11 +2,11 @@ import frappe | |||
def execute(): | |||
frappe.reload_doctype("Web Form") | |||
if not frappe.db.has_column("Web Form", "is_multi_step_form"): | |||
return | |||
for web_form in frappe.get_all("Web Form", filters={"is_multi_step_form": 1}): | |||
web_form_fields = frappe.get_doc("Web Form", web_form.name).web_form_fields | |||
for web_form_field in web_form_fields: | |||
if web_form_field.fieldtype == "Section Break" and web_form_field.idx != 1: | |||
frappe.db.set_value("Web Form Field", web_form_field.name, "fieldtype", "Page Break") | |||
frappe.db.commit() |
@@ -94,6 +94,10 @@ frappe.views.TreeView = class TreeView { | |||
this.page.main.addClass("frappe-card"); | |||
if (this.opts.show_expand_all) { | |||
this.page.add_inner_button(__("Collapse All"), function () { | |||
me.tree.load_children(me.tree.root_node, false); | |||
}); | |||
this.page.add_inner_button(__("Expand All"), function () { | |||
me.tree.load_children(me.tree.root_node, true); | |||
}); | |||
@@ -117,6 +117,7 @@ frappe.views.Workspace = class Workspace { | |||
(page) => page.parent_page == "" || page.parent_page == null | |||
); | |||
} | |||
root_pages = root_pages.uniqBy((d) => d.title); | |||
this.build_sidebar_section(category, root_pages); | |||
}); | |||
@@ -196,8 +196,8 @@ | |||
&.blue { color: var(--blue-500); } | |||
// SIZE & SPACING | |||
margin-top: 12px; | |||
margin-bottom: 5px; | |||
padding-top: 12px; | |||
padding-bottom: 5px; | |||
// LAYOUT | |||
text-align: center; | |||
@@ -98,6 +98,7 @@ class TestAuth(FrappeTestCase): | |||
def test_deny_multiple_login(self): | |||
self.set_system_settings("deny_multiple_sessions", 1) | |||
self.addCleanup(self.set_system_settings, "deny_multiple_sessions", 0) | |||
first_login = FrappeClient(self.HOST_NAME, self.test_user_email, self.test_user_password) | |||
first_login.get_list("ToDo") | |||
@@ -114,6 +115,14 @@ class TestAuth(FrappeTestCase): | |||
second_login.get_list("ToDo") | |||
third_login.get_list("ToDo") | |||
def test_disable_user_pass_login(self): | |||
FrappeClient(self.HOST_NAME, self.test_user_email, self.test_user_password).get_list("ToDo") | |||
self.set_system_settings("disable_user_pass_login", 1) | |||
self.addCleanup(self.set_system_settings, "disable_user_pass_login", 0) | |||
with self.assertRaises(Exception): | |||
FrappeClient(self.HOST_NAME, self.test_user_email, self.test_user_password).get_list("ToDo") | |||
class TestLoginAttemptTracker(FrappeTestCase): | |||
def test_account_lock(self): | |||
@@ -0,0 +1,64 @@ | |||
""" | |||
This file contains multiple primitive tests for avoiding performance regressions. | |||
- Time bound tests: Benchmarks are done on GHA before adding numbers | |||
- Query count tests: More than expected # of queries for any action is frequent source of | |||
performance issues. This guards against such problems. | |||
E.g. We know get_controller is supposed to be cached and hence shouldn't make query post first | |||
query. This test can be written like this. | |||
>>> def test_controller_caching(self): | |||
>>> | |||
>>> get_controller("User") # <- "warm up code" | |||
>>> with self.assertQueryCount(0): | |||
>>> get_controller("User") | |||
""" | |||
import unittest | |||
import frappe | |||
from frappe.model.base_document import get_controller | |||
from frappe.tests.utils import FrappeTestCase | |||
from frappe.website.path_resolver import PathResolver | |||
class TestPerformance(FrappeTestCase): | |||
def reset_request_specific_caches(self): | |||
# To simulate close to request level of handling | |||
frappe.destroy() # releases everything on frappe.local | |||
frappe.init(site=self.TEST_SITE) | |||
frappe.connect() | |||
frappe.clear_cache() | |||
def setUp(self) -> None: | |||
self.reset_request_specific_caches() | |||
def test_meta_caching(self): | |||
frappe.get_meta("User") | |||
with self.assertQueryCount(0): | |||
frappe.get_meta("User") | |||
def test_controller_caching(self): | |||
get_controller("User") | |||
with self.assertQueryCount(0): | |||
get_controller("User") | |||
def test_db_value_cache(self): | |||
"""Link validation if repeated should just use db.value_cache, hence no extra queries""" | |||
doc = frappe.get_last_doc("User") | |||
doc.get_invalid_links() | |||
with self.assertQueryCount(0): | |||
doc.get_invalid_links() | |||
@unittest.skip("Not implemented") | |||
def test_homepage_resolver(self): | |||
paths = ["/", "/app"] | |||
for path in paths: | |||
PathResolver(path).resolve() | |||
with self.assertQueryCount(1): | |||
PathResolver(path).resolve() |
@@ -38,8 +38,7 @@ def create_if_not_exists(doc): | |||
@frappe.whitelist() | |||
def create_todo_records(): | |||
if frappe.get_all("ToDo", {"description": "this is first todo"}): | |||
return | |||
frappe.db.truncate("ToDo") | |||
frappe.get_doc( | |||
{"doctype": "ToDo", "date": add_to_date(now(), days=7), "description": "this is first todo"} | |||
@@ -19,10 +19,13 @@ class FrappeTestCase(unittest.TestCase): | |||
otherwise this class will become ineffective. | |||
""" | |||
TEST_SITE = "test_site" | |||
SHOW_TRANSACTION_COMMIT_WARNINGS = False | |||
@classmethod | |||
def setUpClass(cls) -> None: | |||
cls.TEST_SITE = getattr(frappe.local, "site", None) or cls.TEST_SITE | |||
# flush changes done so far to avoid flake | |||
frappe.db.commit() | |||
frappe.db.begin() | |||
@@ -64,6 +67,21 @@ class FrappeTestCase(unittest.TestCase): | |||
else: | |||
self.assertEqual(expected, actual, msg=msg) | |||
@contextmanager | |||
def assertQueryCount(self, count): | |||
def _sql_with_count(*args, **kwargs): | |||
frappe.db.sql_query_count += 1 | |||
return orig_sql(*args, **kwargs) | |||
try: | |||
orig_sql = frappe.db.sql | |||
frappe.db.sql_query_count = 0 | |||
frappe.db.sql = _sql_with_count | |||
yield | |||
self.assertLessEqual(frappe.db.sql_query_count, count) | |||
finally: | |||
frappe.db.sql = orig_sql | |||
def _commit_watcher(): | |||
import traceback | |||
@@ -289,12 +289,10 @@ def add_standard_navbar_items(): | |||
"is_standard": 1, | |||
}, | |||
{ | |||
"item_label": "Background Jobs", | |||
"item_type": "Route", | |||
"route": "/app/background_jobs", | |||
"item_type": "Separator", | |||
"is_standard": 1, | |||
"item_label": "", | |||
}, | |||
{"item_type": "Separator", "is_standard": 1}, | |||
{ | |||
"item_label": "Log out", | |||
"item_type": "Action", | |||
@@ -167,6 +167,7 @@ def get_safe_globals(): | |||
rollback=frappe.db.rollback, | |||
add_index=frappe.db.add_index, | |||
), | |||
lang=getattr(frappe.local, "lang", "en"), | |||
), | |||
FrappeClient=FrappeClient, | |||
style=frappe._dict(border_color="#d1d8dd"), | |||
@@ -1,6 +1,7 @@ | |||
{% extends "templates/web.html" %} | |||
{% macro email_login_body() -%} | |||
{% if not disable_user_pass_login or (ldap_settings and ldap_settings.enabled) %} | |||
<div class="page-card-body"> | |||
<div class="form-group"> | |||
<label class="form-label sr-only" for="login_email">{{ login_label or _("Email")}}</label> | |||
@@ -38,13 +39,15 @@ | |||
</div> | |||
<p class="forgot-password-message"> | |||
<a href="#forgot">{{ _("Forgot Password?") }}</a></p> | |||
<a href="#forgot">{{ _("Forgot Password?") }}</a> | |||
</p> | |||
</div> | |||
{% endif %} | |||
<div class="page-card-actions"> | |||
{% if not disable_user_pass_login %} | |||
<button class="btn btn-sm btn-primary btn-block btn-login" type="submit"> | |||
{{ _("Login") }}</button> | |||
{% endif %} | |||
{% if ldap_settings and ldap_settings.enabled %} | |||
<button class="btn btn-sm btn-default btn-block btn-login btn-ldap-login"> | |||
{{ _("Login with LDAP") }}</button> | |||
@@ -83,7 +86,9 @@ | |||
{{ email_login_body() }} | |||
</form> | |||
<div class="social-logins text-center"> | |||
{% if not disable_user_pass_login or (ldap_settings and ldap_settings.enabled) %} | |||
<p class="text-muted login-divider">{{ _("or") }}</p> | |||
{% endif %} | |||
<div class="social-login-buttons"> | |||
{% for provider in provider_logins %} | |||
<div class="login-button-wrapper"> | |||
@@ -7,6 +7,7 @@ from frappe import _ | |||
from frappe.auth import LoginManager | |||
from frappe.integrations.doctype.ldap_settings.ldap_settings import LDAPSettings | |||
from frappe.integrations.oauth2_logins import decoder_compat | |||
from frappe.utils import cint | |||
from frappe.utils.html_utils import get_icon_html | |||
from frappe.utils.jinja import guess_is_path | |||
from frappe.utils.oauth import ( | |||
@@ -40,7 +41,8 @@ def get_context(context): | |||
context.for_test = "login.html" | |||
context["title"] = "Login" | |||
context["provider_logins"] = [] | |||
context["disable_signup"] = frappe.utils.cint(frappe.get_website_settings("disable_signup")) | |||
context["disable_signup"] = cint(frappe.get_website_settings("disable_signup")) | |||
context["disable_user_pass_login"] = cint(frappe.get_system_settings("disable_user_pass_login")) | |||
context["logo"] = frappe.get_website_settings("app_logo") or frappe.get_hooks("app_logo_url")[-1] | |||
context["app_name"] = ( | |||
frappe.get_website_settings("app_name") or frappe.get_system_settings("app_name") or _("Frappe") | |||