Skip to content

Commit

Permalink
Merge pull request #22544 from frappe/version-14-hotfix
Browse files Browse the repository at this point in the history
chore: release v14
  • Loading branch information
ankush committed Sep 27, 2023
2 parents 5214465 + 8ae6118 commit a463ca3
Show file tree
Hide file tree
Showing 27 changed files with 259 additions and 75 deletions.
1 change: 1 addition & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = defineConfig({
setupNodeEvents(on, config) {
return require("./cypress/plugins/index.js")(on, config);
},
testIsolation: false,
baseUrl: "http://test_site_ui:8000",
specPattern: ["./cypress/integration/*.js", "**/ui_test_*.js"],
},
Expand Down
3 changes: 0 additions & 3 deletions cypress/integration/control_phone.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ context("Control Phone", () => {
}
);
});

cy.get(".phone-picker").findByRole("searchbox").clear().blur();
cy.get(".phone-section .phone-wrapper").should("not.have.class", "hidden");
});

it("existing document should render phone field with data", () => {
Expand Down
1 change: 0 additions & 1 deletion cypress/integration/customize_form.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ context("Customize Form", () => {
const naming_rule_default_autoname_map = {
"Set by user": "prompt",
"By fieldname": "field:",
'By "Naming Series" field': "naming_series:",
Expression: "format:",
"Expression (old style)": "",
Random: "hash",
Expand Down
7 changes: 4 additions & 3 deletions cypress/integration/login.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
context("Login", () => {
beforeEach(() => {
cy.request("/api/method/logout");
cy.visit("/");
cy.call("logout");
cy.visit("/login");
cy.location("pathname").should("eq", "/login");
});
Expand Down Expand Up @@ -35,7 +36,7 @@ context("Login", () => {
cy.get("#login_password").type(Cypress.env("adminPassword"));

cy.findByRole("button", { name: "Login" }).click();
cy.location("pathname").should("eq", "/app");
cy.location("pathname").should("match", /^\/app/);
cy.window().its("frappe.session.user").should("eq", "Administrator");
});

Expand All @@ -48,7 +49,7 @@ context("Login", () => {
base64_string: "aGVsbG8gYWxs",
});

cy.request("/api/method/logout");
cy.call("logout");

// redirect-to /me page with params to mock OAuth 2.0 like request
cy.visit(
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ context("Navigation", () => {
cy.visit("/app/todo");
cy.get(".page-head").findByTitle("To Do").should("be.visible");
cy.clear_filters();
cy.request("/api/method/logout");
cy.call("logout");
cy.reload().as("reload");
cy.get("@reload").get(".page-card .btn-primary").contains("Login").click();
cy.location("pathname").should("eq", "/login");
Expand Down
4 changes: 2 additions & 2 deletions cypress/integration/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ const attach_file = (file, no_of_files = 1) => {

context("Sidebar", () => {
before(() => {
cy.visit("/login");
cy.visit("/");
cy.login();

cy.visit("/app");
return cy
.window()
.its("frappe")
Expand Down
3 changes: 2 additions & 1 deletion cypress/integration/web_form.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ context("Web Form", () => {

cy.url().should("include", "/note/new");

cy.request("/api/method/logout");
cy.call("logout");
cy.visit("/note");

cy.url().should("include", "/note/new");
Expand All @@ -49,6 +49,7 @@ context("Web Form", () => {
});

it("Login Required", () => {
cy.call("logout");
cy.login("Administrator");
cy.visit("/app/web-form/note");

Expand Down
35 changes: 27 additions & 8 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,30 @@ Cypress.Commands.add("login", (email, password) => {
if (!password) {
password = Cypress.env("adminPassword");
}
cy.request({
url: "/api/method/login",
method: "POST",
body: {
usr: email,
pwd: password,
},
});
// cy.session clears all localStorage on new login, so we need to retain the last route
const session_last_route = window.localStorage.getItem("session_last_route");
return cy
.session(
[email, password] || "",
() => {
return cy.request({
url: "/api/method/login",
method: "POST",
body: {
usr: email,
pwd: password,
},
});
},
{
cacheAcrossSpecs: true,
}
)
.then(() => {
if (session_last_route) {
window.localStorage.setItem("session_last_route", session_last_route);
}
});
});

Cypress.Commands.add("call", (method, args) => {
Expand All @@ -62,6 +78,9 @@ Cypress.Commands.add("call", (method, args) => {
})
.then((res) => {
expect(res.status).eq(200);
if (method === "logout") {
Cypress.session.clearAllSavedSessions();
}
return res.body;
});
});
Expand Down
4 changes: 0 additions & 4 deletions cypress/support/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,3 @@ Cypress.on("uncaught:exception", (err, runnable) => {

// Alternatively you can use CommonJS syntax:
// require('./commands')

Cypress.Cookies.defaults({
preserve: "sid",
});
4 changes: 2 additions & 2 deletions frappe/commands/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -911,10 +911,10 @@ def run_ui_tests(
click.secho("Installing Cypress...", fg="yellow")
packages = " ".join(
[
"cypress@^10",
"cypress@^13",
"@4tw/cypress-drag-drop@^2",
"cypress-real-events",
"@testing-library/cypress@^8",
"@testing-library/cypress@^10",
"@testing-library/dom@8.17.1",
"@cypress/code-coverage@^3",
]
Expand Down
24 changes: 24 additions & 0 deletions frappe/custom/doctype/custom_field/custom_field.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ frappe.ui.form.on("Custom Field", {
frm.toggle_enable("dt", frm.doc.__islocal);
frm.trigger("dt");
frm.toggle_reqd("label", !frm.doc.fieldname);
frm.trigger("add_rename_field");

if (frm.doc.is_system_generated) {
frm.dashboard.add_comment(
Expand Down Expand Up @@ -110,6 +111,29 @@ frappe.ui.form.on("Custom Field", {
frm.fields_dict["options_help"].disp_area.innerHTML = "";
}
},
add_rename_field(frm) {
frm.add_custom_button(__("Rename Fieldname"), () => {
frappe.prompt(
{
fieldtype: "Data",
label: __("Fieldname"),
fieldname: "fieldname",
reqd: 1,
},
function (data) {
frappe.call({
method: "frappe.custom.doctype.custom_field.custom_field.rename_fieldname",
args: {
custom_field: frm.doc.name,
fieldname: data.fieldname,
},
});
},
__("Rename Fieldname"),
__("Rename")
);
});
},
});

frappe.utils.has_special_chars = function (t) {
Expand Down
49 changes: 46 additions & 3 deletions frappe/custom/doctype/custom_field/custom_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,49 @@ def create_custom_fields(custom_fields, ignore_validate=False, update=True):


@frappe.whitelist()
def add_custom_field(doctype, df):
df = json.loads(df)
return create_custom_field(doctype, df)
def rename_fieldname(custom_field: str, fieldname: str):
frappe.only_for("System Manager")

field: CustomField = frappe.get_doc("Custom Field", custom_field)
parent_doctype = field.dt
old_fieldname = field.fieldname
field.fieldname = fieldname
field.set_fieldname()
new_fieldname = field.fieldname

if field.is_system_generated:
frappe.throw(_("System Generated Fields can not be renamed"))
if frappe.db.has_column(parent_doctype, fieldname):
frappe.throw(_("Can not rename as fieldname {0} is already present on DocType."))
if old_fieldname == new_fieldname:
frappe.msgprint(_("Old and new fieldnames are same."), alert=True)
return

frappe.db.rename_column(parent_doctype, old_fieldname, new_fieldname)

# Update in DB after alter column is successful, alter column will implicitly commit, so it's
# best to commit change on field too to avoid any possible mismatch between two.
field.db_set("fieldname", field.fieldname, notify=True)
_update_fieldname_references(field, old_fieldname, new_fieldname)

frappe.db.commit()
frappe.clear_cache()


def _update_fieldname_references(
field: CustomField, old_fieldname: str, new_fieldname: str
) -> None:
# Passwords are stored in auth table, so column name needs to be updated there.
if field.fieldtype == "Password":
Auth = frappe.qb.Table("__Auth")
frappe.qb.update(Auth).set(Auth.fieldname, new_fieldname).where(
(Auth.doctype == field.dt) & (Auth.fieldname == old_fieldname)
).run()

# Update ordering reference.
frappe.db.set_value(
"Custom Field",
{"insert_after": old_fieldname, "dt": field.dt},
"insert_after",
new_fieldname,
)
26 changes: 25 additions & 1 deletion frappe/custom/doctype/custom_field/test_custom_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
# License: MIT. See LICENSE

import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.custom.doctype.custom_field.custom_field import (
create_custom_field,
create_custom_fields,
rename_fieldname,
)
from frappe.tests.utils import FrappeTestCase

test_records = frappe.get_test_records("Custom Field")
Expand Down Expand Up @@ -81,3 +85,23 @@ def test_custom_field_sorting(self):
# undo changes commited by DDL
# nosemgrep
frappe.db.commit()

def test_custom_field_renaming(self):
def gen_fieldname():
return "test_" + frappe.generate_hash()

field = create_custom_field("ToDo", {"label": gen_fieldname()}, is_system_generated=False)
old = field.fieldname
new = gen_fieldname()
data = frappe.generate_hash()
doc = frappe.get_doc({"doctype": "ToDo", old: data, "description": "Something"}).insert()

rename_fieldname(field.name, new)
field.reload()
self.assertEqual(field.fieldname, new)

doc = frappe.get_doc("ToDo", doc.name) # doc.reload doesn't clear old fields.
self.assertEqual(doc.get(new), data)
self.assertFalse(doc.get(old))

field.delete()
3 changes: 3 additions & 0 deletions frappe/database/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,9 @@ def enqueue_jobs_after_commit():
)
frappe.flags.enqueue_after_commit = []

def rename_column(self, doctype: str, old_column_name: str, new_column_name: str):
raise NotImplementedError


@contextmanager
def savepoint(catch: type | tuple[type, ...] = Exception):
Expand Down
14 changes: 14 additions & 0 deletions frappe/database/mariadb/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,20 @@ def change_column_type(
null_constraint = "NOT NULL" if not nullable else ""
return self.sql_ddl(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} {null_constraint}")

def rename_column(self, doctype: str, old_column_name, new_column_name):
current_data_type = self.get_column_type(doctype, old_column_name)

table_name = get_table_name(doctype)

frappe.db.sql_ddl(
f"""ALTER TABLE `{table_name}`
CHANGE COLUMN `{old_column_name}`
`{new_column_name}`
{current_data_type}"""
# ^ Mariadb requires passing current data type again even if there's no change
# This requirement is gone from v10.5
)

def create_auth_table(self):
self.sql_ddl(
"""create table if not exists `__Auth` (
Expand Down
6 changes: 6 additions & 0 deletions frappe/database/postgres/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ def change_column_type(
ALTER COLUMN "{column}" {null_constraint}"""
)

def rename_column(self, doctype: str, old_column_name: str, new_column_name: str):
table_name = get_table_name(doctype)
frappe.db.sql_ddl(
f"ALTER TABLE `{table_name}` RENAME COLUMN `{old_column_name}` TO `{new_column_name}`"
)

def create_auth_table(self):
self.sql_ddl(
"""create table if not exists "__Auth" (
Expand Down
5 changes: 3 additions & 2 deletions frappe/desk/doctype/dashboard_chart/dashboard_chart.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,14 +280,15 @@
"options": "DocType"
},
{
"description": "If set, only user with these roles can access this chart. If not set, DocType or Report permissions will be used.",
"fieldname": "roles",
"fieldtype": "Table",
"label": "Roles",
"options": "Has Role"
}
],
"links": [],
"modified": "2023-08-14 16:33:30.172798",
"modified": "2023-09-18 13:41:05.263676",
"modified_by": "Administrator",
"module": "Desk",
"name": "Dashboard Chart",
Expand Down Expand Up @@ -332,4 +333,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}
11 changes: 5 additions & 6 deletions frappe/desk/doctype/dashboard_chart/dashboard_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,18 @@ def has_permission(doc, ptype, user):
if "System Manager" in roles:
return True

if doc.chart_type == "Report":
if doc.roles:
allowed = [d.role for d in doc.roles]
if has_common(roles, allowed):
return True
elif doc.chart_type == "Report":
if doc.report_name in get_allowed_report_names():
return True
else:
allowed_doctypes = frappe.permissions.get_doctypes_with_read()
if doc.document_type in allowed_doctypes:
return True

if doc.roles:
allowed = [d.role for d in doc.roles]
if has_common(roles, allowed):
return True

return False


Expand Down
6 changes: 4 additions & 2 deletions frappe/desk/doctype/number_card/number_card.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,14 @@ frappe.ui.form.on("Number Card", {
set_method_description: function (frm) {
if (frm.doc.type == "Custom") {
frm.fields_dict.method.set_description(`
Set the path to a whitelisted function that will return the number on the card in the format:
Set the path to a whitelisted function that will return the data for the number card in the format:
<pre class="small text-muted">
<code>
{
"value": value,
"fieldtype": "Currency"
"fieldtype": "Currency",
"route_options": {"from_date": "2023-05-23"},
"route": ["query-report", "Permitted Documents For User"]
}
</code></pre>`);
}
Expand Down
Loading

0 comments on commit a463ca3

Please sign in to comment.