Skip to content

Commit

Permalink
Merge pull request #25874 from frappe/version-14-hotfix
Browse files Browse the repository at this point in the history
chore: release v14
  • Loading branch information
ankush committed Apr 9, 2024
2 parents c3350b8 + f32b5d8 commit 803d89c
Show file tree
Hide file tree
Showing 27 changed files with 125 additions and 129 deletions.
44 changes: 0 additions & 44 deletions cypress/integration/api.js

This file was deleted.

4 changes: 1 addition & 3 deletions cypress/integration/list_view.js
Expand Up @@ -29,13 +29,11 @@ context("List View", () => {
const actions = [
"Approve",
"Reject",
"Edit",
"Export",
"Assign To",
"Apply Assignment Rule",
"Add Tags",
"Print",
"Delete",
];
cy.go_to_list("ToDo");
cy.clear_filters();
Expand All @@ -45,7 +43,7 @@ context("List View", () => {
});
cy.get(".actions-btn-group button").contains("Actions").should("be.visible").click();
cy.get(".dropdown-menu li:visible .dropdown-item")
.should("have.length", 9)
.should("have.length", 7)
.each((el, index) => {
cy.wrap(el).contains(actions[index]);
})
Expand Down
4 changes: 4 additions & 0 deletions frappe/commands/site.py
Expand Up @@ -1138,6 +1138,10 @@ def browse(context, site, user=None):

sid = ""
if user:
if not frappe.db.exists("User", user):
click.echo(f"User {user} does not exist")
sys.exit(1)

if frappe.conf.developer_mode or user == "Administrator":
frappe.utils.set_request(path="/")
frappe.local.cookie_manager = CookieManager()
Expand Down
3 changes: 2 additions & 1 deletion frappe/core/doctype/doctype/test_doctype.py
Expand Up @@ -8,6 +8,7 @@
from frappe.cache_manager import clear_doctype_cache
from frappe.core.doctype.doctype.doctype import (
CannotIndexedError,
DocType,
DoctypeLinkError,
HiddenAndMandatoryWithoutDefaultError,
IllegalMandatoryError,
Expand Down Expand Up @@ -719,7 +720,7 @@ def new_doctype(
custom: bool = True,
default: str | None = None,
**kwargs,
):
) -> "DocType":
if not name:
# Test prefix is required to avoid coverage
name = "Test " + "".join(random.sample(string.ascii_lowercase, 10))
Expand Down
5 changes: 4 additions & 1 deletion frappe/core/doctype/file/file.py
Expand Up @@ -765,10 +765,13 @@ def get_permission_query_conditions(user: str | None = None) -> str:
if user == "Administrator":
return ""

if frappe.get_cached_value("User", user, "user_type") != "System User":
return f""" `tabFile`.`owner` = {frappe.db.escape(user)} """

readable_doctypes = ", ".join(repr(dt) for dt in get_doctypes_with_read())
return f"""
(`tabFile`.`is_private` = 0)
OR (`tabFile`.`attached_to_doctype` IS NULL AND `tabFile`.`owner` = {user !r})
OR (`tabFile`.`attached_to_doctype` IS NULL AND `tabFile`.`owner` = {frappe.db.escape(user)})
OR (`tabFile`.`attached_to_doctype` IN ({readable_doctypes}))
"""

Expand Down
36 changes: 0 additions & 36 deletions frappe/core/doctype/file/test_file.py
Expand Up @@ -664,45 +664,9 @@ def test_list_private_attachments(self):
self.assertNotIn("test_user_standalone.txt", system_manager_files)

self.assertIn("test_sm_attachment.txt", system_manager_attachments_files)
self.assertIn("test_sm_attachment.txt", user_attachments_files)
self.assertIn("test_user_attachment.txt", system_manager_attachments_files)
self.assertIn("test_user_attachment.txt", user_attachments_files)

def test_list_public_single_file(self):
"""Ensure that users are able to list public standalone files."""
frappe.set_user("test@example.com")
frappe.get_doc(
{
"doctype": "File",
"file_name": "test_public_single.txt",
"content": "Public single File",
"is_private": 0,
}
).insert()

frappe.set_user("test4@example.com")
files = [file.file_name for file in get_files_in_folder("Home")["files"]]
self.assertIn("test_public_single.txt", files)

def test_list_public_attachment(self):
"""Ensure that users are able to list public attachments."""
frappe.set_user("test@example.com")
self.attached_to_doctype, self.attached_to_docname = make_test_doc()
frappe.get_doc(
{
"doctype": "File",
"file_name": "test_public_attachment.txt",
"attached_to_doctype": self.attached_to_doctype,
"attached_to_name": self.attached_to_docname,
"content": "Public Attachment",
"is_private": 0,
}
).insert()

frappe.set_user("test4@example.com")
files = [file.file_name for file in get_files_in_folder("Home/Attachments")["files"]]
self.assertIn("test_public_attachment.txt", files)

def tearDown(self) -> None:
frappe.set_user("Administrator")
frappe.db.rollback()
Expand Down
10 changes: 8 additions & 2 deletions frappe/core/doctype/scheduled_job_type/scheduled_job_type.py
Expand Up @@ -2,7 +2,8 @@
# License: MIT. See LICENSE

import json
from datetime import datetime
from datetime import datetime, timedelta
from random import randint

import click
from croniter import CroniterBadCronError, croniter
Expand Down Expand Up @@ -97,7 +98,12 @@ def get_next_execution(self):
# immediately, even when it's meant to be daily.
# A dynamic fallback like current time might miss the scheduler interval and job will never start.
last_execution = get_datetime(self.last_execution or self.creation)
return croniter(self.cron_format, last_execution).get_next(datetime)
next_execution = croniter(self.cron_format, last_execution).get_next(datetime)

jitter = 0
if self.frequency in ("Hourly Long", "Daily Long"):
jitter = randint(1, 600)
return next_execution + timedelta(seconds=jitter)

def execute(self):
self.scheduler_log = None
Expand Down
4 changes: 2 additions & 2 deletions frappe/core/doctype/server_script/server_script.json
Expand Up @@ -52,7 +52,7 @@
"fieldname": "doctype_event",
"fieldtype": "Select",
"label": "DocType Event",
"options": "Before Insert\nBefore Validate\nBefore Save\nAfter Insert\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)\nOn Payment Authorization"
"options": "Before Insert\nBefore Validate\nBefore Save\nAfter Insert\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)\nBefore Print\nOn Payment Authorization"
},
{
"depends_on": "eval:doc.script_type==='API'",
Expand Down Expand Up @@ -118,7 +118,7 @@
"link_fieldname": "server_script"
}
],
"modified": "2022-06-13 06:04:20.937969",
"modified": "2024-04-08 16:18:52.901097",
"modified_by": "Administrator",
"module": "Core",
"name": "Server Script",
Expand Down
1 change: 1 addition & 0 deletions frappe/core/doctype/server_script/server_script_utils.py
Expand Up @@ -17,6 +17,7 @@
"after_delete": "After Delete",
"before_update_after_submit": "Before Save (Submitted Document)",
"on_update_after_submit": "After Save (Submitted Document)",
"before_print": "Before Print",
"on_payment_authorized": "On Payment Authorization",
}

Expand Down
5 changes: 5 additions & 0 deletions frappe/database/mariadb/schema.py
Expand Up @@ -90,6 +90,11 @@ def alter(self):
if not frappe.db.get_column_index(self.table_name, col.fieldname, unique=False):
add_index_query.append(f"ADD INDEX `{col.fieldname}_index`(`{col.fieldname}`)")

if self.meta.sort_field == "creation" and frappe.db.get_column_index(
self.table_name, "creation", unique=False
):
add_index_query.append("ADD INDEX `creation`(`creation`)")

for col in {*self.drop_index, *self.drop_unique}:
if col.fieldname == "name":
continue
Expand Down
2 changes: 1 addition & 1 deletion frappe/desk/query_report.py
Expand Up @@ -340,7 +340,7 @@ def export_query():
if isinstance(visible_idx, str):
visible_idx = json.loads(visible_idx)

data = run(report_name, form_params.filters, custom_columns=custom_columns)
data = run(report_name, form_params.filters, custom_columns=custom_columns, are_default_filters=False)
data = frappe._dict(data)
if not data.columns:
frappe.respond_as_web_page(
Expand Down
22 changes: 13 additions & 9 deletions frappe/integrations/utils.py
Expand Up @@ -6,24 +6,28 @@
from urllib.parse import parse_qs

import frappe
from frappe import _
from frappe.utils import get_request_session


def make_request(method, url, auth=None, headers=None, data=None):
def make_request(method, url, auth=None, headers=None, data=None, json=None, params=None):
auth = auth or ""
data = data or {}
headers = headers or {}

try:
s = get_request_session()
frappe.flags.integration_request = s.request(method, url, data=data, auth=auth, headers=headers)
frappe.flags.integration_request.raise_for_status()

if frappe.flags.integration_request.headers.get("content-type") == "text/plain; charset=utf-8":
return parse_qs(frappe.flags.integration_request.text)

return frappe.flags.integration_request.json()
response = frappe.flags.integration_request = s.request(
method, url, data=data, auth=auth, headers=headers, json=json, params=params
)
content_type = response.headers.get("content-type")
if content_type == "text/plain; charset=utf-8":
return parse_qs(response.text)
elif content_type.startswith("application/") and content_type.split(";")[0].endswith("json"):
return response.json()
elif response.text:
return response.text
else:
return
except Exception as exc:
frappe.log_error()
raise exc
Expand Down
3 changes: 3 additions & 0 deletions frappe/model/base_document.py
Expand Up @@ -933,6 +933,9 @@ def _validate_length(self):
self.throw_length_exceeded_error(df, max_length, value)

elif column_type in ("int", "bigint", "smallint"):
if cint(df.get("length")) > 11: # We implicitl switch to bigint for >11
column_type = "bigint"

max_length = max_positive_value[column_type]

if abs(cint(value)) > max_length:
Expand Down
4 changes: 0 additions & 4 deletions frappe/permissions.py
Expand Up @@ -291,10 +291,6 @@ def has_user_permission(doc, user=None):
# no user permission rules specified for this doctype
return True

# user can create own role permissions, so nothing applies
if get_role_permissions("User Permission", user=user).get("write"):
return True

# don't apply strict user permissions for single doctypes since they contain empty link fields
apply_strict_user_permissions = (
False if doc.meta.issingle else frappe.get_system_settings("apply_strict_user_permissions")
Expand Down
2 changes: 1 addition & 1 deletion frappe/public/js/frappe/form/controls/datetime.js
Expand Up @@ -16,7 +16,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co
}

get_start_date() {
this.value = this.value == null ? undefined : this.value;
this.value = this.value == null || this.value == "" ? undefined : this.value;
let value = frappe.datetime.convert_to_user_tz(this.value);
return frappe.datetime.str_to_obj(value);
}
Expand Down
16 changes: 16 additions & 0 deletions frappe/public/js/frappe/form/controls/table.js
Expand Up @@ -24,6 +24,13 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control
const doctype = grid.doctype;
const row_docname = $(e.target).closest(".grid-row").data("name");
const in_grid_form = $(e.target).closest(".form-in-grid").length;
const value_formatter_map = {
Date: (val) => (val ? frappe.datetime.user_to_str(val) : val),
Int: (val) => cint(val),
Check: (val) => cint(val),
Float: (val) => flt(val),
Currency: (val) => flt(val),
};

let pasted_data = frappe.utils.get_clipboard_data(e);

Expand All @@ -34,10 +41,13 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control
if (data.length === 1 && data[0].length === 1) return;

let fieldnames = [];
let fieldtypes = [];
// for raw data with column header
if (this.get_field(data[0][0])) {
data[0].forEach((column) => {
fieldnames.push(this.get_field(column));
const df = frappe.meta.get_docfield(doctype, this.get_field(column));
fieldtypes.push(df ? df.fieldtype : "");
});
data.shift();
} else {
Expand All @@ -51,6 +61,8 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control
column.fieldname === $(e.target).data("fieldname")
) {
fieldnames.push(column.fieldname);
const df = frappe.meta.get_docfield(doctype, column.fieldname);
fieldtypes.push(df ? df.fieldtype : "");
target_column_matched = true;
}
});
Expand All @@ -73,6 +85,10 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control
const row_name = grid_rows[row_idx - 1].doc.name;
row.forEach((value, data_index) => {
if (fieldnames[data_index]) {
// format value before setting
value = value_formatter_map[fieldtypes[data_index]]
? value_formatter_map[fieldtypes[data_index]](value)
: value;
frappe.model.set_value(
doctype,
row_name,
Expand Down
4 changes: 2 additions & 2 deletions frappe/public/js/frappe/form/grid.js
Expand Up @@ -213,7 +213,7 @@ export default class Grid {
this.df.data = this.get_data();
this.df.data = this.df.data.filter((row) => row.idx != doc.idx);
}
this.grid_rows_by_docname[doc.name].remove();
this.grid_rows_by_docname[doc.name]?.remove();
dirty = true;
});
tasks.push(() => frappe.timeout(0.1));
Expand Down Expand Up @@ -758,7 +758,7 @@ export default class Grid {
}

set_value(fieldname, value, doc) {
if (this.display_status !== "None" && this.grid_rows_by_docname[doc.name]) {
if (this.display_status !== "None" && doc.name && this.grid_rows_by_docname[doc.name]) {
this.grid_rows_by_docname[doc.name].refresh_field(fieldname, value);
}
}
Expand Down
10 changes: 9 additions & 1 deletion frappe/public/js/frappe/form/layout.js
Expand Up @@ -205,6 +205,10 @@ frappe.ui.form.Layout = class Layout {

const parent = this.column.wrapper.get(0);
const fieldobj = this.init_field(df, parent, render);

// An invalid control name will return in a null fieldobj
if (!fieldobj) return;

this.fields_list.push(fieldobj);
this.fields_dict[df.fieldname] = fieldobj;

Expand All @@ -230,7 +234,11 @@ frappe.ui.form.Layout = class Layout {
layout: this,
});

fieldobj.layout = this;
// make_control can return null for invalid control names
if (fieldobj) {
fieldobj.layout = this;
}

return fieldobj;
}

Expand Down

0 comments on commit 803d89c

Please sign in to comment.