Skip to content

Commit

Permalink
Merge pull request #25875 from frappe/version-15-hotfix
Browse files Browse the repository at this point in the history
chore: release v15
  • Loading branch information
ankush committed Apr 9, 2024
2 parents 9495f2c + f918416 commit bcc5817
Show file tree
Hide file tree
Showing 37 changed files with 310 additions and 248 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 @@ -22,20 +22,18 @@ 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();
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)
.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 @@ -1189,6 +1189,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
2 changes: 1 addition & 1 deletion frappe/contacts/doctype/address/address.py
Expand Up @@ -348,7 +348,7 @@ def get_address_display_list(doctype: str, name: str) -> list[dict]:
["Dynamic Link", "parenttype", "=", "Address"],
],
fields=["*"],
order_by="is_primary_address DESC, creation ASC",
order_by="is_primary_address DESC, `tabAddress`.creation ASC",
)
for a in address_list:
a["display"] = get_address_display(a)
Expand Down
2 changes: 1 addition & 1 deletion frappe/contacts/doctype/contact/contact.py
Expand Up @@ -386,7 +386,7 @@ def get_contact_display_list(doctype: str, name: str) -> list[dict]:
["Dynamic Link", "parenttype", "=", "Contact"],
],
fields=["*"],
order_by="is_primary_contact DESC, creation ASC",
order_by="is_primary_contact DESC, `tabContact`.creation ASC",
)

for contact in contact_list:
Expand Down
3 changes: 2 additions & 1 deletion frappe/core/doctype/doctype/test_doctype.py
Expand Up @@ -10,6 +10,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 @@ -782,7 +783,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
7 changes: 5 additions & 2 deletions frappe/core/doctype/file/file.py
Expand Up @@ -15,7 +15,7 @@
from frappe import _
from frappe.database.schema import SPECIAL_CHAR_PATTERN
from frappe.model.document import Document
from frappe.permissions import get_doctypes_with_read
from frappe.permissions import SYSTEM_USER_ROLE, get_doctypes_with_read
from frappe.utils import call_hook_method, cint, get_files_path, get_hook_method, get_url
from frappe.utils.file_manager import is_safe_path
from frappe.utils.image import optimize_image, strip_exif_data
Expand Down Expand Up @@ -823,10 +823,13 @@ def get_permission_query_conditions(user: str | None = None) -> str:
if user == "Administrator":
return ""

if SYSTEM_USER_ROLE not in frappe.get_roles(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
32 changes: 0 additions & 32 deletions frappe/core/doctype/file/test_file.py
Expand Up @@ -681,41 +681,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.new_doc(
"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.new_doc(
"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 @@ -123,7 +124,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 @@ -57,7 +57,7 @@
"fieldname": "doctype_event",
"fieldtype": "Select",
"label": "DocType Event",
"options": "Before Insert\nBefore Validate\nBefore Save\nAfter Insert\nAfter Save\nBefore Rename\nAfter Rename\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 Rename\nAfter Rename\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 @@ -151,7 +151,7 @@
"link_fieldname": "server_script"
}
],
"modified": "2024-02-27 11:44:46.397495",
"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.py
Expand Up @@ -46,6 +46,7 @@ class ServerScript(Document):
"After Delete",
"Before Save (Submitted Document)",
"After Save (Submitted Document)",
"Before Print",
"On Payment Authorization",
]
enable_rate_limit: DF.Check
Expand Down
1 change: 1 addition & 0 deletions frappe/core/doctype/server_script/server_script_utils.py
Expand Up @@ -19,6 +19,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
8 changes: 5 additions & 3 deletions frappe/core/doctype/user/test_user.py
Expand Up @@ -459,7 +459,7 @@ def test_reset_password_link_expiry(self):

class TestImpersonation(FrappeAPITestCase):
def test_impersonation(self):
with test_user(roles=["System Manager"]) as user:
with test_user(roles=["System Manager"], commit=True) as user:
self.post(
self.method_path("frappe.core.doctype.user.user.impersonate"),
{"user": user.name, "reason": "test", "sid": self.sid},
Expand All @@ -469,7 +469,9 @@ def test_impersonation(self):


@contextmanager
def test_user(*, first_name: str | None = None, email: str | None = None, roles: list[str], **kwargs):
def test_user(
*, first_name: str | None = None, email: str | None = None, roles: list[str], commit=False, **kwargs
):
try:
first_name = first_name or frappe.generate_hash()
email = email or (first_name + "@example.com")
Expand All @@ -485,7 +487,7 @@ def test_user(*, first_name: str | None = None, email: str | None = None, roles:
yield user
finally:
user.delete(force=True, ignore_permissions=True)
frappe.db.commit()
commit and frappe.db.commit()


def delete_contact(user):
Expand Down
2 changes: 1 addition & 1 deletion frappe/database/db_manager.py
Expand Up @@ -25,7 +25,7 @@ def delete_user(self, target, host=None):
def create_database(self, target):
if target in self.get_database_list():
self.drop_database(target)
self.db.sql(f"CREATE DATABASE `{target}`")
self.db.sql(f"CREATE DATABASE `{target}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")

def drop_database(self, target):
self.db.sql_ddl(f"DROP DATABASE IF EXISTS `{target}`")
Expand Down
6 changes: 6 additions & 0 deletions frappe/database/mariadb/schema.py
Expand Up @@ -80,6 +80,12 @@ def alter(self):
for col in self.add_index
if not frappe.db.get_column_index(self.table_name, col.fieldname, unique=False)
]

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`)")

drop_index_query = []

for col in {*self.drop_index, *self.drop_unique}:
Expand Down
33 changes: 1 addition & 32 deletions frappe/database/mariadb/setup_db.py
Expand Up @@ -5,11 +5,6 @@
import frappe
from frappe.database.db_manager import DbManager

REQUIRED_MARIADB_CONFIG = {
"character_set_server": "utf8mb4",
"collation_server": "utf8mb4_unicode_ci",
}


def get_mariadb_variables():
return frappe._dict(frappe.db.sql("show variables"))
Expand Down Expand Up @@ -70,9 +65,7 @@ def bootstrap_database(db_name, verbose, source_sql=None):
import sys

frappe.connect(db_name=db_name)
if not check_database_settings():
print("Database settings do not match expected values; stopping database setup.")
sys.exit(1)
check_compatible_versions()

import_db_from_sql(source_sql, verbose)

Expand Down Expand Up @@ -104,30 +97,6 @@ def import_db_from_sql(source_sql=None, verbose=False):
print("Imported from database %s" % source_sql)


def check_database_settings():
check_compatible_versions()

# Check each expected value vs. actuals:
mariadb_variables = get_mariadb_variables()
result = True
for key, expected_value in REQUIRED_MARIADB_CONFIG.items():
if mariadb_variables.get(key) != expected_value:
print(f"For key {key}. Expected value {expected_value}, found value {mariadb_variables.get(key)}")
result = False

if not result:
print(
(
"{sep2}Creation of your site - {site} failed because MariaDB is not properly {sep}"
"configured.{sep2}"
"Please verify the above settings in MariaDB's my.cnf. Restart MariaDB.{sep}"
"And then run `bench new-site {site}` again.{sep2}"
).format(site=frappe.local.site, sep2="\n\n", sep="\n")
)

return result


def check_compatible_versions():
try:
version = get_mariadb_version()
Expand Down
11 changes: 5 additions & 6 deletions frappe/desk/doctype/dashboard_chart/dashboard_chart.py
Expand Up @@ -260,7 +260,7 @@ def get_heatmap_chart_config(chart, filters, heatmap_year):
}


def get_group_by_chart_config(chart, filters):
def get_group_by_chart_config(chart, filters) -> dict | None:
aggregate_function = get_aggregate_function(chart.group_by_type)
value_field = chart.aggregate_function_based_on or "1"
group_by_field = chart.group_by_based_on
Expand All @@ -281,11 +281,10 @@ def get_group_by_chart_config(chart, filters):

if data:
return {
"labels": [item["name"] if item["name"] else "Not Specified" for item in data],
"labels": [item.get("name", "Not Specified") for item in data],
"datasets": [{"name": chart.name, "values": [item["count"] for item in data]}],
}
else:
return None
return None


def get_aggregate_function(chart_type):
Expand All @@ -304,8 +303,8 @@ def get_result(data, timegrain, from_date, to_date, chart_type):
for d in result:
count = 0
while data_index < len(data) and getdate(data[data_index][0]) <= d[0]:
d[1] += data[data_index][1]
count += data[data_index][2]
d[1] += cint(data[data_index][1])
count += cint(data[data_index][2])
data_index += 1
if chart_type == "Average" and not count == 0:
d[1] = d[1] / count
Expand Down

0 comments on commit bcc5817

Please sign in to comment.