diff --git a/cypress/integration/view_routing.js b/cypress/integration/view_routing.js
index 72fb6836ec..5942d4f005 100644
--- a/cypress/integration/view_routing.js
+++ b/cypress/integration/view_routing.js
@@ -224,8 +224,8 @@ context("View", () => {
});
});
- it("Route to Settings Workspace", () => {
- cy.visit("/app/settings");
- cy.get(".title-text").should("contain", "Settings");
+ it("Route to Website Workspace", () => {
+ cy.visit("/app/website");
+ cy.get(".title-text").should("contain", "Website");
});
});
diff --git a/cypress/integration/workspace.js b/cypress/integration/workspace.js
index 1499c8772e..4079877c9c 100644
--- a/cypress/integration/workspace.js
+++ b/cypress/integration/workspace.js
@@ -7,8 +7,8 @@ context("Workspace 2.0", () => {
it("Navigate to page from sidebar", () => {
cy.visit("/app/build");
cy.get(".codex-editor__redactor .ce-block");
- cy.get('.sidebar-item-container[item-name="Settings"]').first().click();
- cy.location("pathname").should("eq", "/app/settings");
+ cy.get('.sidebar-item-container[item-name="Website"]').first().click();
+ cy.location("pathname").should("eq", "/app/website");
});
it("Create Private Page", () => {
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 16db0a4477..3c83ed335f 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -1002,19 +1002,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
diff --git a/frappe/boot.py b/frappe/boot.py
index 2ce950a55a..d0e6204e78 100644
--- a/frappe/boot.py
+++ b/frappe/boot.py
@@ -270,9 +270,6 @@ def get_user_info():
user_info = frappe._dict()
add_user_info(frappe.session.user, user_info)
- if frappe.session.user == "Administrator" and user_info.Administrator.email:
- user_info[user_info.Administrator.email] = user_info.Administrator
-
return user_info
diff --git a/frappe/core/doctype/communication/communication.js b/frappe/core/doctype/communication/communication.js
index a36af705a7..86ef59f994 100644
--- a/frappe/core/doctype/communication/communication.js
+++ b/frappe/core/doctype/communication/communication.js
@@ -267,6 +267,7 @@ frappe.ui.form.on("Communication", {
$.extend(args, {
subject: __("Re: {0}", [frm.doc.subject]),
recipients: frm.doc.sender,
+ is_a_reply: true,
});
new frappe.views.CommunicationComposer(args);
@@ -278,6 +279,7 @@ frappe.ui.form.on("Communication", {
subject: __("Res: {0}", [frm.doc.subject]),
recipients: frm.doc.sender,
cc: frm.doc.cc,
+ is_a_reply: true,
});
new frappe.views.CommunicationComposer(args);
},
@@ -287,6 +289,7 @@ frappe.ui.form.on("Communication", {
$.extend(args, {
forward: true,
subject: __("Fw: {0}", [frm.doc.subject]),
+ is_a_reply: true,
});
new frappe.views.CommunicationComposer(args);
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index f8720eb5be..3c3008ecd6 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -234,6 +234,7 @@ class DocType(Document):
"DocPerm",
"Custom Field",
"Customize Form Field",
+ "Web Form Field",
"DocField",
]
diff --git a/frappe/core/doctype/recorder/recorder.json b/frappe/core/doctype/recorder/recorder.json
index aa0d782811..391f808f31 100644
--- a/frappe/core/doctype/recorder/recorder.json
+++ b/frappe/core/doctype/recorder/recorder.json
@@ -14,6 +14,7 @@
"cmd",
"time",
"duration",
+ "event_type",
"section_break_1skt",
"request_headers",
"section_break_sgro",
@@ -30,6 +31,7 @@
"label": "Path"
},
{
+ "depends_on": "eval:doc.event_type==\"HTTP Request\"",
"fieldname": "cmd",
"fieldtype": "Data",
"in_standard_filter": 1,
@@ -67,6 +69,7 @@
"fieldtype": "Section Break"
},
{
+ "depends_on": "eval:doc.event_type==\"HTTP Request\"",
"fieldname": "request_headers",
"fieldtype": "Code",
"label": "Request Headers"
@@ -76,11 +79,13 @@
"fieldtype": "Section Break"
},
{
+ "depends_on": "eval:doc.event_type==\"HTTP Request\"",
"fieldname": "form_dict",
"fieldtype": "Code",
"label": "Form Dict"
},
{
+ "depends_on": "eval:doc.event_type==\"HTTP Request\"",
"fieldname": "method",
"fieldtype": "Select",
"in_standard_filter": 1,
@@ -96,6 +101,12 @@
{
"fieldname": "section_break_9jhm",
"fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "event_type",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Event Type"
}
],
"hide_toolbar": 1,
@@ -103,7 +114,7 @@
"index_web_pages_for_search": 1,
"is_virtual": 1,
"links": [],
- "modified": "2023-08-10 12:01:03.456643",
+ "modified": "2024-01-03 16:45:47.110048",
"modified_by": "Administrator",
"module": "Core",
"name": "Recorder",
diff --git a/frappe/core/doctype/recorder/recorder.py b/frappe/core/doctype/recorder/recorder.py
index f5ef909a2a..347a237743 100644
--- a/frappe/core/doctype/recorder/recorder.py
+++ b/frappe/core/doctype/recorder/recorder.py
@@ -19,6 +19,7 @@ class Recorder(Document):
cmd: DF.Data | None
duration: DF.Float
+ event_type: DF.Data | None
form_dict: DF.Code | None
method: DF.Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
number_of_queries: DF.Int
@@ -27,7 +28,6 @@ class Recorder(Document):
sql_queries: DF.Table[RecorderQuery]
time: DF.Datetime | None
time_in_queries: DF.Float
-
# end: auto-generated types
def load_from_db(self):
diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py
index e84e0dd712..ce86da5e91 100644
--- a/frappe/custom/doctype/custom_field/custom_field.py
+++ b/frappe/custom/doctype/custom_field/custom_field.py
@@ -362,7 +362,8 @@ def rename_fieldname(custom_field: str, fieldname: str):
frappe.msgprint(_("Old and new fieldnames are same."), alert=True)
return
- frappe.db.rename_column(parent_doctype, old_fieldname, new_fieldname)
+ if frappe.db.has_column(field.dt, old_fieldname):
+ 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.
diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py
index 9fc12a1e0d..eec086f3bb 100644
--- a/frappe/desk/doctype/number_card/number_card.py
+++ b/frappe/desk/doctype/number_card/number_card.py
@@ -10,7 +10,7 @@ from frappe.model.naming import append_number_if_name_exists
from frappe.modules.export_file import export_to_files
from frappe.query_builder import Criterion
from frappe.query_builder.utils import DocType
-from frappe.utils import cint
+from frappe.utils import cint, flt
class NumberCard(Document):
@@ -165,7 +165,7 @@ def get_result(doc, filters, to_date=None):
)
number = res[0]["result"] if res else 0
- return cint(number)
+ return flt(number)
@frappe.whitelist()
diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py
index 2f4c23eac6..0fa328b42d 100755
--- a/frappe/email/doctype/email_account/email_account.py
+++ b/frappe/email/doctype/email_account/email_account.py
@@ -268,15 +268,15 @@ class EmailAccount(Document):
if not in_receive and self.use_imap:
email_server.imap.logout()
- # reset failed attempts count
- self.set_failed_attempts_count(0)
-
return email_server
def check_email_server_connection(self, email_server, in_receive):
# tries to connect to email server and handles failure
try:
email_server.connect()
+
+ # reset failed attempts count - do it after succesful connection
+ self.set_failed_attempts_count(0)
except (error_proto, imaplib.IMAP4.error) as e:
message = cstr(e).lower().replace(" ", "")
auth_error_codes = [
@@ -294,6 +294,8 @@ class EmailAccount(Document):
error_message = _(
"Authentication failed while receiving emails from Email Account: {0}."
).format(self.name)
+
+ error_message = _("Email Account Disabled.") + " " + error_message
error_message += "
" + _("Message from server: {0}").format(cstr(e))
self.handle_incoming_connect_error(description=error_message)
return None
@@ -489,31 +491,35 @@ class EmailAccount(Document):
state.pop("_smtp_server_instance", None)
def handle_incoming_connect_error(self, description):
- if self.get_failed_attempts_count() > 2:
- self.db_set("enable_incoming", 0)
-
- for user in get_system_managers(only_name=True):
- try:
- assign_to.add(
- {
- "assign_to": user,
- "doctype": self.doctype,
- "name": self.name,
- "description": description,
- "priority": "High",
- "notify": 1,
- }
- )
- except assign_to.DuplicateToDoError:
- frappe.clear_last_message()
+ if self.get_failed_attempts_count() > 5:
+ # This is done in background to avoid committing here.
+ frappe.enqueue(self._disable_broken_incoming_account, description=description)
else:
self.set_failed_attempts_count(self.get_failed_attempts_count() + 1)
+ def _disable_broken_incoming_account(self, description):
+ self.db_set("enable_incoming", 0)
+
+ for user in get_system_managers(only_name=True):
+ try:
+ assign_to.add(
+ {
+ "assign_to": [user],
+ "doctype": self.doctype,
+ "name": self.name,
+ "description": description,
+ "priority": "High",
+ "notify": 1,
+ }
+ )
+ except assign_to.DuplicateToDoError:
+ pass
+
def set_failed_attempts_count(self, value):
- frappe.cache.set(f"{self.name}:email-account-failed-attempts", value)
+ frappe.cache.set_value(f"{self.name}:email-account-failed-attempts", value)
def get_failed_attempts_count(self):
- return cint(frappe.cache.get(f"{self.name}:email-account-failed-attempts"))
+ return cint(frappe.cache.get_value(f"{self.name}:email-account-failed-attempts"))
def receive(self):
"""Called by scheduler to receive emails from this EMail account using POP3/IMAP."""
diff --git a/frappe/hooks.py b/frappe/hooks.py
index e82714389b..c7bcd9895c 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -428,6 +428,7 @@ before_request = [
# Background Job Hooks
before_job = [
+ "frappe.recorder.record",
"frappe.monitor.start",
]
@@ -438,6 +439,7 @@ if os.getenv("FRAPPE_SENTRY_DSN") and (
before_job.append("frappe.utils.sentry.set_sentry_context")
after_job = [
+ "frappe.recorder.dump",
"frappe.monitor.stop",
"frappe.utils.file_lock.release_document_locks",
"frappe.utils.telemetry.flush",
diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py
index 3ecfe6cd61..64d38a2ae7 100644
--- a/frappe/integrations/doctype/webhook/webhook.py
+++ b/frappe/integrations/doctype/webhook/webhook.py
@@ -153,15 +153,14 @@ def get_context(doc):
def enqueue_webhook(doc, webhook) -> None:
+ request_url = headers = data = None
try:
webhook: Webhook = frappe.get_doc("Webhook", webhook.get("name"))
- headers = get_webhook_headers(doc, webhook)
- data = get_webhook_data(doc, webhook)
-
+ request_url = webhook.request_url
if webhook.is_dynamic_url:
request_url = frappe.render_template(webhook.request_url, get_context(doc))
- else:
- request_url = webhook.request_url
+ headers = get_webhook_headers(doc, webhook)
+ data = get_webhook_data(doc, webhook)
except Exception as e:
frappe.logger().debug({"enqueue_webhook_error": e})
diff --git a/frappe/permissions.py b/frappe/permissions.py
index a334cc5722..c54637a77c 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import copy
+import functools
import frappe
import frappe.share
@@ -37,17 +38,23 @@ AUTOMATIC_ROLES = (GUEST_ROLE, ALL_USER_ROLE, SYSTEM_USER_ROLE, ADMIN_ROLE)
def print_has_permission_check_logs(func):
+ @functools.wraps(func)
def inner(*args, **kwargs):
- frappe.flags["has_permission_check_logs"] = []
- result = func(*args, **kwargs)
- self_perm_check = True if not kwargs.get("user") else kwargs.get("user") == frappe.session.user
raise_exception = kwargs.get("raise_exception", True)
+ self_perm_check = True if not kwargs.get("user") else kwargs.get("user") == frappe.session.user
+
+ if raise_exception:
+ frappe.flags["has_permission_check_logs"] = []
+
+ result = func(*args, **kwargs)
# print only if access denied
# and if user is checking his own permission
if not result and self_perm_check and raise_exception:
msgprint(("
").join(frappe.flags.get("has_permission_check_logs", [])))
- frappe.flags.pop("has_permission_check_logs", None)
+
+ if raise_exception:
+ frappe.flags.pop("has_permission_check_logs", None)
return result
return inner
@@ -163,9 +170,6 @@ def get_doc_permissions(doc, user=None, ptype=None):
if not user:
user = frappe.session.user
- if frappe.is_table(doc.doctype):
- return {"read": 1, "write": 1}
-
meta = frappe.get_meta(doc.doctype)
def is_user_owner():
diff --git a/frappe/public/js/frappe/form/controls/autocomplete.js b/frappe/public/js/frappe/form/controls/autocomplete.js
index 825cfb5320..b7bf5687d6 100644
--- a/frappe/public/js/frappe/form/controls/autocomplete.js
+++ b/frappe/public/js/frappe/form/controls/autocomplete.js
@@ -85,33 +85,15 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui
};
}
- init_option_cache() {
- if (!this.$input.cache) {
- this.$input.cache = {};
- }
- if (!this.$input.cache[this.doctype]) {
- this.$input.cache[this.doctype] = {};
- }
- if (!this.$input.cache[this.doctype][this.df.fieldname]) {
- this.$input.cache[this.doctype][this.df.fieldname] = {};
- }
- }
-
setup_awesomplete() {
this.awesomplete = new Awesomplete(this.input, this.get_awesomplete_settings());
$(this.input_area).find(".awesomplete ul").css("min-width", "100%");
- this.init_option_cache();
-
this.$input.on(
"input",
frappe.utils.debounce((e) => {
- const cached_options =
- this.$input.cache[this.doctype][this.df.fieldname][e.target.value];
- if (cached_options && cached_options.length) {
- this.set_data(cached_options);
- } else if (this.get_query || this.df.get_query) {
+ if (this.get_query || this.df.get_query) {
this.execute_query_if_exists(e.target.value);
} else {
this.awesomplete.list = this.get_data();
@@ -245,7 +227,6 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui
if (!this.$input.is(":focus")) {
return;
}
- this.$input.cache[this.doctype][this.df.fieldname][term] = message;
this.set_data(message);
},
});
diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js
index ba913e53f0..d403becaeb 100644
--- a/frappe/public/js/frappe/form/grid_row.js
+++ b/frappe/public/js/frappe/form/grid_row.js
@@ -429,10 +429,10 @@ export default class GridRow {
$(`