any updates for version 13.
lifesaver! Exactly what I was looking for. I needed to disable the amounts in CoA as it’s using too much computing resource for very big transactions, when we press expand all.
I used the above method (by revant_one) to override the validate method of EmployeeCheckin doctype. It worked perfectly.
But, something else broke. I have a custom script (JS) on after_save of EmployeeCheckin. This code isn’t executed anymore. Can anyone explain why this happens? How do I get some front end code running on after_save?
I was once told (i don’t remember in what thread) that after_save hook will be triggered no matter how the doc is inserted/saved.
So if it is not (as is my case) it might be a bug. Report it on the github.
refer to this app GitHub - aakvatech/CSF_TZ for monkey patching any method
- copy and adapt this file into your own custom app
CSF_TZ/__init__.py at master · aakvatech/CSF_TZ · GitHub - create python file under monkey_patches folder like this file.
CSF_TZ/db_transaction_writes.py at master · aakvatech/CSF_TZ · GitHub
How to override python method which isn’t inside class?
for example, get_leaves_for_period
method inside Leave Application
package?
I tried:
myapp/myapp/hr/leave_application.py
from erpnext.hr.doctype.leave_application import leave_application
def get_leaves_for_period_new(employee, leave_type, from_date, to_date, do_not_skip_expired_leaves=False):
frappe.throw(_("hello"))
def override(doc=None, target=None):
leave_application.get_pending_leaves_for_period = get_pending_leaves_for_period_new
then in hooks.py
doc_events = {
"Leave Application": {
"validate": "myapp.myapp.hr.leave_application.override",
"onload": "myapp.myapp.hr.leave_application.override",
"refresh": "myapp.myapp.hr.leave_application.override"
}
and this approach not working.
ye, but this is about overriding doctype class, but method get_pending_leaves_for_period
inside leave_application
is not a class method, it’s a method which is inside leave_application.py
but not in Leave Application
class.
Did you check this one ?
It uses
import frappe.utils.data
import custom_money_format.modified_scripts.data
frappe.utils.fmt_money = custom_money_format.modified_scripts.data.custom_fmt_money
in the hooks.py.
Yes, also…
my hooks.py:
import erpnext.hr.doctype.leave_application.leave_application
import myapp.myapp.hr.leave_application
erpnext.hr.doctype.leave_application.leave_application.get_pending_leaves_for_period = myapp.myapp.hr.leave_application.get_pending_leaves_for_period_new
and not working
ah… there was a problem somewhere else…
i realised that i override this function again with main one in some other place… (donkey)
sorry, nevermind.
I would like to mention 3 things:
-
My solution was inspired by other one from this thread. Thank you, guys.
-
The other solution used init.py instead of hooks.py, but that has a side effect: if you uninstall the app, the patches are still loaded (after restart). It may be a bug in frappe, or a feature in python - I don’t know, yet. Using hooks.py solves the problem.
-
If your patch doesn’t work or works in selected places only, check one more thing. Some functions can be called in more than one namespace. For example: frappe.utils.data.fmt_money, or frappe.utils.fmt_money (without “data”). That’s because the functions are imported in module’s init file (from [something] import *). I lost a few hours searching for the bug… The solution: assign your custom function to both namespaced versions.
A lot of great information in here. I have my doc_events working.
My question is : Is there an event that is triggered when a NewDoc Entry (like Payment Entry when you add a new entry) is created?
I see that there is a “onload” event and many others, but is there a “oncreated” or “onadded”? To override the validate function as soon as a new doc of that type is made?
I am trying to override the validate function for Payment Entry, I want to override its validate function as soon as a new payment is added, the problem I am having is that most of the events that I am aware of are triggered AFTER the original validate function is ran which does not help overriding.
Thank you for any feedback!
I’ve seen an incomplete event list in this documentation:
To make things more complicated, I’m trying to override non-class method get_timesheet_details() in report/billing_summary.py.
My story:
I’m using Custom App, of which in hooks.py I overrode erpnext.payroll.doctype.salary_slip.salary_slip.set_time_sheet() method. This is working fine as it is inside the SalarySlip class.
Now, myapp.salary_slip.set_time_sheet() calls erpnext.projects.report.billing_summary.get_timesheet_details(). Unfortunately I need to override this method too (see this issue)
In myapp/salary_slip.py, I import:
from erpnext.projects.report.billing_summary import get_timesheet_details
and therefore it will call the non-overridden get_timesheet_details().
In myapp/hooks.py:
import erpnext.projects.report.billing_summary
import myapp_app.my_app.billing_summary
erpnext.projects.report.billing_summary.get_timesheet_details =
my_app.my_app.billing_summary.get_timesheet_details
The workaround I’m using is, in myapp/salary_slip.py, instead of this:
from erpnext.projects.report.billing_summary import get_timesheet_details
I use:
from my_app.my_app.billing_summary import get_timesheet_details
PS: I’m using monkey patch for the above fixes.
“monkey patch” ?
You can find some inspiration here :
check __init__.py
Sure not sustainable way to do it but work
EDIT : Sorry didn’t check previous post that give CSF_TZ repo with is also a good inpiration source for monkey patch
@FHenry hello! I’m using monkey patch already. Sorry I didn’t mention that.
I’ve just edited my reply above.
this monkey patch is not same as in CSF_TZ repo and https://gitee.com/yuzelin/erpnext_oob/tree/master/erpnext_oob
how it works
in aap.init.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import importlib
import frappe
patches_loaded = False
__version__ = '13.0.0'
def console(*data):
frappe.publish_realtime("out_to_console", data, user=frappe.session.user)
def load_monkey_patches():
global patches_loaded
if (
patches_loaded
or not getattr(frappe, "conf", None)
or not "erpnext_oob" in frappe.get_installed_apps()
):
return
for app in frappe.get_installed_apps():
if app in ['frappe', 'erpnext']: continue
folder = frappe.get_app_path(app, "monkey_patches")
if not os.path.exists(folder): continue
for module_name in os.listdir(folder):
if not module_name.endswith(".py") or module_name == "__init__.py":
continue
importlib.import_module(f"{app}.monkey_patches.{module_name[:-3]}")
patches_loaded = True
connect = frappe.connect
def custom_connect(*args, **kwargs):
out = connect(*args, **kwargs)
if frappe.conf.auto_commit_on_many_writes:
frappe.db.auto_commit_on_many_writes = 1
load_monkey_patches()
return out
frappe.connect = custom_connect
create monkey_patches folder under your app folder.
create any py file, monkey patch like below
import frappe
from frappe.utils import cstr
from frappe.model.base_document import BaseDocument
import json
def get_owner_username(self):
return frappe.db.get_value('User', self.owner, 'full_name')
def get_submit_username(self):
"""变更记录data字段数据格式
changed:[[其它字段,旧值,新值]
['docstatus', 0, 1]
]"""
try:
if not self.meta.is_submittable:
return
filters={'ref_doctype': self.doctype, 'docname': self.name, 'data': ('like', '%docstatus%')}
version_list = frappe.get_all('Version', filters = filters, fields=['owner','data'], order_by="creation desc")
for version in version_list:
data = json.loads(version.data)
found = [f for f in data.get('changed') if f[0] =='docstatus' and f[-1] ==1]
if found:
return frappe.db.get_value('User', version.owner, 'full_name')
except:
pass
def _validate_selects(self):
if frappe.flags.in_import:
return
for df in self.meta.get_select_fields():
if df.fieldname=="naming_series" or not (self.get(df.fieldname) and df.options):
continue
options = (df.options or "").split("\n")
#支持分号(;)分隔的值与标签,以解决下拉值一词多义问题
options = [o.split(";")[0] for o in options if o]
# if only empty options
if not filter(None, options):
continue
# strip and set
self.set(df.fieldname, cstr(self.get(df.fieldname)).strip())
value = self.get(df.fieldname)
if value not in options and not (frappe.flags.in_test and value.startswith("_T-")):
# show an elaborate message
prefix = _("Row #{0}:").format(self.idx) if self.get("parentfield") else ""
label = _(self.meta.get_label(df.fieldname))
comma_options = '", "'.join(_(each) for each in options)
frappe.throw(_('{0} {1} cannot be "{2}". It should be one of "{3}"').format(prefix, label,
value, comma_options))
BaseDocument.get_owner_username = get_owner_username
BaseDocument.get_submit_username = get_submit_username
BaseDocument._validate_selects = _validate_selects
you can check the source code from the repository.
Yes, It worked
i need to change frappe.utils.format_date function but none of hooks.py method and monkey_patching method worked for me!!!
in second method when i use function just after load_monkey_patches()
, value has been returned from my new function but in other places such as site, value returned from frappe.
any idea?