From 6d45b500a117f4bf928377b88eeb4da15ef1b019 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 29 Oct 2022 12:25:57 +0000 Subject: [PATCH] perf: load `_doc_before_save` sooner to avoid DB call in `check_if_latest` (#18666) * perf: load `_doc_before_save` sooner to avoid DB calls in `check_if_latest` * fix: specify `for update` in `load_doc_before_save` --- frappe/model/document.py | 73 ++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/frappe/model/document.py b/frappe/model/document.py index 936d9b049c38..2a2f924b00d6 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -245,6 +245,7 @@ def insert( self._set_defaults() self.set_user_and_timestamp() self.set_docstatus() + self.load_doc_before_save() self.check_if_latest() self._validate_links() self.check_permission("create") @@ -325,6 +326,7 @@ def _save(self, ignore_permissions=None, ignore_version=None) -> "Document": self.set_user_and_timestamp() self.set_docstatus() + self.load_doc_before_save() self.check_if_latest() self.set_parent_in_children() self.set_name_in_children() @@ -743,49 +745,24 @@ def check_if_latest(self): Will also validate document transitions (Save > Submit > Cancel) calling `self.check_docstatus_transition`.""" - conflict = False - self._action = "save" - if not self.get("__islocal") and not self.meta.get("is_virtual"): - if self.meta.issingle: - modified = frappe.db.sql( - """select value from tabSingles - where doctype=%s and field='modified' for update""", - self.doctype, - ) - modified = modified and modified[0][0] - if modified and modified != cstr(self._original_modified): - conflict = True - else: - tmp = frappe.db.sql( - """select modified, docstatus from `tab{}` - where name = %s for update""".format( - self.doctype - ), - self.name, - as_dict=True, - ) - - if not tmp: - frappe.throw(_("Record does not exist")) - else: - tmp = tmp[0] - modified = cstr(tmp.modified) + self._action = "save" + previous = self.get_doc_before_save() - if modified and modified != cstr(self._original_modified): - conflict = True + if not previous or self.meta.get("is_virtual"): + self.check_docstatus_transition(0) + return - self.check_docstatus_transition(tmp.docstatus) + if cstr(previous.modified) != cstr(self._original_modified): + frappe.msgprint( + _("Error: Document has been modified after you have opened it") + + (f" ({previous.modified}, {self.modified}). ") + + _("Please refresh to get the latest document."), + raise_exception=frappe.TimestampMismatchError, + ) - if conflict: - frappe.msgprint( - _("Error: Document has been modified after you have opened it") - + (f" ({modified}, {self.modified}). ") - + _("Please refresh to get the latest document."), - raise_exception=frappe.TimestampMismatchError, - ) - else: - self.check_docstatus_transition(0) + if not self.meta.issingle: + self.check_docstatus_transition(previous.docstatus) def check_docstatus_transition(self, to_docstatus): """Ensures valid `docstatus` transition. @@ -1049,7 +1026,6 @@ def run_before_save_methods(self): Will also update title_field if set""" - self.load_doc_before_save() self.reset_seen() # before_validate method should be executed before ignoring validations @@ -1073,14 +1049,17 @@ def run_before_save_methods(self): self.set_title_field() def load_doc_before_save(self): - """Save load document from db before saving""" + """load existing document from db before saving""" + self._doc_before_save = None - if not self.is_new(): - try: - self._doc_before_save = frappe.get_doc(self.doctype, self.name) - except frappe.DoesNotExistError: - self._doc_before_save = None - frappe.clear_last_message() + + if self.is_new(): + return + + try: + self._doc_before_save = frappe.get_doc(self.doctype, self.name, for_update=True) + except frappe.DoesNotExistError: + frappe.clear_last_message() def run_post_save_methods(self): """Run standard methods after `INSERT` or `UPDATE`. Standard Methods are: