diff --git a/frappe/__init__.py b/frappe/__init__.py index 97e605394b..07f75ecd31 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -354,11 +354,11 @@ def cache() -> "RedisWrapper": return redis_server -def get_traceback(): +def get_traceback(with_context=False): """Returns error traceback.""" from frappe.utils import get_traceback - return get_traceback() + return get_traceback(with_context=with_context) def errprint(msg): @@ -1210,18 +1210,35 @@ def reload_doc(module, dt=None, dn=None, force=False, reset_permissions=False): @whitelist() -def rename_doc(*args, **kwargs): +def rename_doc( + doctype: str, + old: str, + new: str, + force: bool = False, + merge: bool = False, + *, + ignore_if_exists: bool = False, + show_alert: bool = True, + rebuild_search: bool = True, +) -> str: """ Renames a doc(dt, old) to doc(dt, new) and updates all linked fields of type "Link" Calls `frappe.model.rename_doc.rename_doc` """ - kwargs.pop("ignore_permissions", None) - kwargs.pop("cmd", None) from frappe.model.rename_doc import rename_doc - return rename_doc(*args, **kwargs) + return rename_doc( + doctype=doctype, + old=old, + new=new, + force=force, + merge=merge, + ignore_if_exists=ignore_if_exists, + show_alert=show_alert, + rebuild_search=rebuild_search, + ) def get_module(modulename): @@ -2069,7 +2086,6 @@ def logger( def log_error(title=None, message=None, reference_doctype=None, reference_name=None): """Log error to Error Log""" - # Parameter ALERT: # the title and message may be swapped # the better API for this is log_error(title, message), and used in many cases this way @@ -2082,20 +2098,15 @@ def log_error(title=None, message=None, reference_doctype=None, reference_name=N else: traceback = message - if not traceback: - traceback = get_traceback() - - if not title: - title = "Error" + title = title or "Error" + traceback = as_unicode(traceback or get_traceback(with_context=True)) return get_doc( - dict( - doctype="Error Log", - error=as_unicode(traceback), - method=title, - reference_doctype=reference_doctype, - reference_name=reference_name, - ) + doctype="Error Log", + error=traceback, + method=title, + reference_doctype=reference_doctype, + reference_name=reference_name, ).insert(ignore_permissions=True) diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 61410fb1a8..0c9b87e618 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -68,6 +68,8 @@ "prepared_report_section", "enable_prepared_report_auto_deletion", "prepared_report_expiry_period", + "column_break_64", + "max_auto_email_report_per_user", "system_updates_section", "disable_system_update_notification" ], @@ -445,7 +447,7 @@ "collapsible": 1, "fieldname": "prepared_report_section", "fieldtype": "Section Break", - "label": "Prepared Report" + "label": "Reports" }, { "default": "Frappe", @@ -485,12 +487,22 @@ "fieldtype": "Select", "label": "First Day of the Week", "options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday" + }, + { + "fieldname": "column_break_64", + "fieldtype": "Column Break" + }, + { + "default": "20", + "fieldname": "max_auto_email_report_per_user", + "fieldtype": "Int", + "label": "Max auto email report per user" } ], "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2022-01-04 11:28:34.881192", + "modified": "2022-04-21 09:11:35.218721", "modified_by": "Administrator", "module": "Core", "name": "System Settings", diff --git a/frappe/database/database.py b/frappe/database/database.py index d4677a1295..fb631951fa 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -1066,7 +1066,7 @@ class Database(object): now_datetime() - relativedelta(minutes=minutes), )[0][0] - def get_db_table_columns(self, table): + def get_db_table_columns(self, table) -> List[str]: """Returns list of column names from given table.""" columns = frappe.cache().hget("table_columns", table) if columns is None: @@ -1146,18 +1146,13 @@ class Database(object): return frappe.db.is_missing_column(e) def get_descendants(self, doctype, name): - """Return descendants of the current record""" - node_location_indexes = self.get_value(doctype, name, ("lft", "rgt")) - if node_location_indexes: - lft, rgt = node_location_indexes - return self.sql_list( - """select name from `tab{doctype}` - where lft > {lft} and rgt < {rgt}""".format( - doctype=doctype, lft=lft, rgt=rgt - ) - ) - else: - # when document does not exist + """Return descendants of the group node in tree""" + from frappe.utils.nestedset import get_descendants_of + + try: + return get_descendants_of(doctype, name, ignore_permissions=True) + except Exception: + # Can only happen if document doesn't exists - kept for backward compatibility return [] def is_missing_table_or_column(self, e): diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index 7a9af6149a..9f897a1308 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -12,6 +12,7 @@ from frappe.model.document import Document from frappe.model.naming import append_number_if_name_exists from frappe.utils import ( add_to_date, + cint, format_time, get_link_to_form, get_url_to_report, @@ -51,14 +52,18 @@ class AutoEmailReport(Document): self.email_to = "\n".join(valid) def validate_report_count(self): - """check that there are only 3 enabled reports per user""" - count = frappe.db.sql( - "select count(*) from `tabAuto Email Report` where user=%s and enabled=1", self.user - )[0][0] - max_reports_per_user = frappe.local.conf.max_reports_per_user or 3 + count = frappe.db.count("Auto Email Report", {"user": self.user, "enabled": 1}) + + max_reports_per_user = ( + cint(frappe.local.conf.max_reports_per_user) # kept for backward compatibilty + or cint(frappe.db.get_single_value("System Settings", "max_auto_email_report_per_user")) + or 20 + ) if count > max_reports_per_user + (-1 if self.flags.in_insert else 0): - frappe.throw(_("Only {0} emailed reports are allowed per user").format(max_reports_per_user)) + msg = _("Only {0} emailed reports are allowed per user.").format(max_reports_per_user) + msg += " " + _("To allow more reports update limit in System Settings.") + frappe.throw(msg, title=_("Report limit reached")) def validate_report_format(self): """check if user has select correct report format""" diff --git a/frappe/hooks.py b/frappe/hooks.py index d3de3877ba..f7a67dc7ec 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -226,7 +226,6 @@ scheduler_events = { "frappe.sessions.clear_expired_sessions", "frappe.email.doctype.notification.notification.trigger_daily_alerts", "frappe.utils.scheduler.restrict_scheduler_events_if_dormant", - "frappe.email.doctype.auto_email_report.auto_email_report.send_daily", "frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.remove_unverified_record", "frappe.desk.form.document_follow.send_daily_updates", "frappe.social.doctype.energy_point_settings.energy_point_settings.allocate_review_points", @@ -241,6 +240,7 @@ scheduler_events = { "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily", "frappe.utils.change_log.check_for_update", "frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_daily", + "frappe.email.doctype.auto_email_report.auto_email_report.send_daily", "frappe.integrations.doctype.google_drive.google_drive.daily_backup", ], "weekly_long": [ diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 02eb2ab38c..f8d60d0763 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -2,6 +2,7 @@ # License: MIT. See LICENSE import datetime import json +from typing import Dict, List import frappe from frappe import _ @@ -252,7 +253,7 @@ class BaseDocument(object): def get_valid_dict( self, sanitize=True, convert_dates_to_str=False, ignore_nulls=False, ignore_virtual=False - ): + ) -> Dict: d = frappe._dict() for fieldname in self.meta.get_valid_columns(): d[fieldname] = self.get(fieldname) @@ -329,7 +330,7 @@ class BaseDocument(object): if key not in self.__dict__: self.__dict__[key] = None - def get_valid_columns(self): + def get_valid_columns(self) -> List[str]: if self.doctype not in frappe.local.valid_columns: if self.doctype in DOCTYPES_FOR_DOCTYPE: from frappe.model.meta import get_table_columns @@ -342,7 +343,7 @@ class BaseDocument(object): return frappe.local.valid_columns[self.doctype] - def is_new(self): + def is_new(self) -> bool: return self.get("__islocal") @property @@ -359,7 +360,7 @@ class BaseDocument(object): no_default_fields=False, convert_dates_to_str=False, no_child_table_fields=False, - ): + ) -> Dict: doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str) doc["doctype"] = self.doctype diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 4bba8ae7ad..a1c3dce91f 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -289,19 +289,23 @@ export default class GridRow { var me = this; if(this.doc && !this.grid.df.in_place_edit) { // remove row - if(!this.open_form_button) { - this.open_form_button = $(` -