Skip to content

Commit

Permalink
Merge branch 'version-14' of https://github.com/frappe/frappe into ve…
Browse files Browse the repository at this point in the history
…rsion-14
  • Loading branch information
aliriocastro committed Jan 11, 2024
2 parents 43646bd + a9c8a1d commit 8abb8d9
Show file tree
Hide file tree
Showing 139 changed files with 2,028 additions and 1,640 deletions.
1 change: 1 addition & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ ignore =
E713,
E712,
B028,
W604,

max-line-length = 200
exclude=,test_*.py
3 changes: 3 additions & 0 deletions cypress/integration/control_date.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ context("Date Control", () => {
function get_dialog(date_field_options) {
return cy.dialog({
title: "Date",
animate: false,
fields: [
{
label: "Date",
Expand Down Expand Up @@ -75,6 +76,8 @@ context("Date Control", () => {

//Verifying if clicking on "Today" button matches today's date
cy.window().then((win) => {
// `expect` can not wait like `should`
cy.wait(500);
expect(win.cur_dialog.fields_dict.date.value).to.be.equal(
win.frappe.datetime.get_today()
);
Expand Down
20 changes: 15 additions & 5 deletions cypress/integration/control_phone.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ context("Control Phone", () => {
cy.visit("/app/website");
});

afterEach(() => {
cy.clear_dialogs();
});

function get_dialog_with_phone() {
return cy.dialog({
title: "Phone",
Expand All @@ -20,31 +24,37 @@ context("Control Phone", () => {

it("should set flag and data", () => {
get_dialog_with_phone().as("dialog");

cy.get(".selected-phone").click();
cy.wait(100);
cy.get(".phone-picker .phone-wrapper[id='afghanistan']").click();
cy.wait(100);
cy.get(".selected-phone .country").should("have.text", "+93");
cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/af.svg");

cy.get(".selected-phone").click();
cy.wait(100);
cy.get(".phone-picker .phone-wrapper[id='india']").click();
cy.wait(100);
cy.get(".selected-phone .country").should("have.text", "+91");
cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/in.svg");

let phone_number = "9312672712";
cy.get(".selected-phone > img").click().first();
cy.get_field("phone").first().click({ multiple: true });
cy.get_field("phone").first().click();
cy.get(".frappe-control[data-fieldname=phone]")
.findByRole("textbox")
.first()
.type(phone_number, { force: true });
.type(phone_number);

cy.get_field("phone").first().should("have.value", phone_number);
cy.get_field("phone").first().blur({ force: true });
cy.get_field("phone").first().blur();
cy.wait(100);
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("phone");
expect(value).to.equal("+91-" + phone_number);
});
});

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);
Expand Down
14 changes: 3 additions & 11 deletions frappe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
)
from .utils.lazy_loader import lazy_import

__version__ = "14.57.0"
__version__ = "14.62.2"
__title__ = "Frappe Framework"

controllers = {}
Expand Down Expand Up @@ -973,19 +973,11 @@ def has_permission(
)

if throw and not out:
# mimics frappe.throw
document_label = (
f"{_(doctype)} {doc if isinstance(doc, str) else doc.name}" if doc else _(doctype)
)
msgprint(
_("No permission for {0}").format(document_label),
raise_exception=ValidationError,
title=None,
indicator="red",
is_minimizable=None,
wide=None,
as_list=False,
)
frappe.flags.error_message = _("No permission for {0}").format(document_label)
raise frappe.PermissionError

return out

Expand Down
13 changes: 13 additions & 0 deletions frappe/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from frappe import _
from frappe.desk.reportview import validate_args
from frappe.model.db_query import check_parent_permission
from frappe.model.utils import is_virtual_doctype
from frappe.utils import get_safe_filters

if TYPE_CHECKING:
Expand Down Expand Up @@ -437,6 +438,18 @@ def validate_link(doctype: str, docname: str, fields=None):
)

values = frappe._dict()

if is_virtual_doctype(doctype):
try:
frappe.get_doc(doctype, docname)
values.name = docname
except frappe.DoesNotExistError:
frappe.clear_last_message()
frappe.msgprint(
_("Document {0} {1} does not exist").format(frappe.bold(doctype), frappe.bold(docname)),
)
return values

values.name = frappe.db.get_value(doctype, docname, cache=True)

fields = frappe.parse_json(fields)
Expand Down
132 changes: 132 additions & 0 deletions frappe/commands/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,136 @@ def fix_whitespaces(text):
click.echo(frappe.as_json(summary_dict))


@click.command("add-database-index")
@click.option("--doctype", help="DocType on which index needs to be added")
@click.option(
"--column",
multiple=True,
help="Column to index. Multiple columns will create multi-column index in given order. To create a multiple, single column index, execute the command multiple times.",
)
@pass_context
def add_db_index(context, doctype, column):
"Adds a new DB index and creates a property setter to persist it."
from frappe.custom.doctype.property_setter.property_setter import make_property_setter

columns = column # correct naming
for site in context.sites:
frappe.connect(site=site)
try:
frappe.db.add_index(doctype, columns)
if len(columns) == 1:
make_property_setter(
doctype,
columns[0],
property="search_index",
value="1",
property_type="Check",
for_doctype=False, # Applied on docfield
)
frappe.db.commit()
finally:
frappe.destroy()

if not context.sites:
raise SiteNotSpecifiedError


@click.command("describe-database-table")
@click.option("--doctype", help="DocType to describe")
@click.option(
"--column",
multiple=True,
help="Explicitly fetch accurate cardinality from table data. This can be quite slow on large tables.",
)
@pass_context
def describe_database_table(context, doctype, column):
"""Describes various statistics about the table.
This is useful to build integration like
This includes:
1. Schema
2. Indexes
3. stats - total count of records
4. if column is specified then extra stats are generated for column:
Distinct values count in column
"""
import json

for site in context.sites:
frappe.connect(site=site)
try:
data = _extract_table_stats(doctype, column)
# NOTE: Do not print anything else in this to avoid clobbering the output.
print(json.dumps(data, indent=2))
finally:
frappe.destroy()

if not context.sites:
raise SiteNotSpecifiedError


def _extract_table_stats(doctype: str, columns: list[str]) -> dict:
from frappe.utils import cint, cstr, get_table_name

def sql_bool(val):
return cstr(val).lower() in ("yes", "1", "true")

table = get_table_name(doctype, wrap_in_backticks=True)

schema = []
for field in frappe.db.sql(f"describe {table}", as_dict=True):
schema.append(
{
"column": field["Field"],
"type": field["Type"],
"is_nullable": sql_bool(field["Null"]),
"default": field["Default"],
}
)

def update_cardinality(column, value):
for col in schema:
if col["column"] == column:
col["cardinality"] = value
break

indexes = []
for idx in frappe.db.sql(f"show index from {table}", as_dict=True):
indexes.append(
{
"unique": not sql_bool(idx["Non_unique"]),
"cardinality": idx["Cardinality"],
"name": idx["Key_name"],
"sequence": idx["Seq_in_index"],
"nullable": sql_bool(idx["Null"]),
"column": idx["Column_name"],
"type": idx["Index_type"],
}
)
if idx["Seq_in_index"] == 1:
update_cardinality(idx["Column_name"], idx["Cardinality"])

total_rows = cint(
frappe.db.sql(
f"""select table_rows
from information_schema.tables
where table_name = 'tab{doctype}'"""
)[0][0]
)

# fetch accurate cardinality for columns by query. WARN: This can take a lot of time.
for column in columns:
cardinality = frappe.db.sql(f"select count(distinct {column}) from {table}")[0][0]
update_cardinality(column, cardinality)

return {
"table_name": table.strip("`"),
"total_rows": total_rows,
"schema": schema,
"indexes": indexes,
}


@click.command("add-system-manager")
@click.argument("email")
@click.option("--first-name")
Expand Down Expand Up @@ -1359,6 +1489,8 @@ def add_new_user(
commands = [
add_system_manager,
add_user_for_sites,
add_db_index,
describe_database_table,
backup,
drop_site,
install_app,
Expand Down
6 changes: 3 additions & 3 deletions frappe/contacts/doctype/contact/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ def autoname(self):
filter(None, [cstr(self.get(f)).strip() for f in ["first_name", "last_name"]])
)

if frappe.db.exists("Contact", self.name):
self.name = append_number_if_name_exists("Contact", self.name)

# concat party name if reqd
for link in self.links:
self.name = self.name + "-" + link.link_name.strip()
break

if frappe.db.exists("Contact", self.name):
self.name = append_number_if_name_exists("Contact", self.name)

def validate(self):
self.set_primary_email()
self.set_primary("phone")
Expand Down
76 changes: 67 additions & 9 deletions frappe/core/doctype/audit_trail/audit_trail.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@

frappe.ui.form.on("Audit Trail", {
refresh(frm) {
let prev_route = frappe.get_prev_route();
if (
prev_route.length > 2 &&
prev_route[0] == "Form" &&
!prev_route.includes("Audit Trail")
) {
frm.set_value("doctype_name", prev_route[1]);
frm.set_value("document", prev_route[2]);
frm.set_value("start_date", "");
frm.set_value("end_date", "");
if (frm.doc.doctype_name && frm.doc.document)
frm.events.get_audit_trail_for_document(frm);
}

frm.page.clear_indicator();

frm.disable_save();
Expand All @@ -16,17 +30,61 @@ frappe.ui.form.on("Audit Trail", {
};
});

frm.set_query("document", () => {
let filters = {
amended_from: ["!=", ""],
};
if (frm.doc.start_date && frm.doc.end_date)
filters["creation"] = ["between", [frm.doc.start_date, frm.doc.end_date]];
else if (frm.doc.start_date) filters["creation"] = [">=", frm.doc.start_date];
else if (frm.doc.end_date) filters["creation"] = ["<=", frm.doc.end_date];
return {
filters: filters,
};
});

frm.page.set_primary_action("Compare", () => {
frm.call({
doc: frm.doc,
method: "compare_document",
callback: function (r) {
let document_names = r.message[0];
let changed_fields = r.message[1];
frm.events.render_changed_fields(frm, document_names, changed_fields);
frm.events.render_rows_added_or_removed(frm, changed_fields);
},
frm.events.get_audit_trail_for_document(frm);
});
},

start_date(frm) {
if (frm.doc.start_date > frm.doc.end_date) {
frm.doc.end_date = "";
frm.refresh_fields();
}

frappe.db
.get_value(frm.doc.doctype_name, frm.doc.document, "creation")
.then((creation) => {
if (frappe.datetime.obj_to_str(creation) < frm.doc.start_date) {
frm.doc.document = "";
frm.refresh_fields();
}
});
},

end_date(frm) {
frappe.db
.get_value(frm.doc.doctype_name, frm.doc.document, "creation")
.then((creation) => {
if (frappe.datetime.obj_to_str(creation) > frm.doc.end_date) {
frm.doc.document = "";
frm.refresh_fields();
}
});
},

get_audit_trail_for_document(frm) {
frm.call({
doc: frm.doc,
method: "compare_document",
callback: function (r) {
let document_names = r.message[0];
let changed_fields = r.message[1];
frm.events.render_changed_fields(frm, document_names, changed_fields);
frm.events.render_rows_added_or_removed(frm, changed_fields);
},
});
},

Expand Down
Loading

0 comments on commit 8abb8d9

Please sign in to comment.