How to Override Standard DocType Methods in Frappe/ERPNext
Override Exiting Python MethodsLearn how to dynamically override or 'monkey patch' core DocType methods in Frappe and ERPNext using a custom Python script, demonstrated with the Sales Invoice DocType.
In Frappe development, there are times when you need to alter the core behavior of a standard DocType beyond what is possible with standard hooks. This snippet demonstrates a powerful technique known as 'monkey patching', where you dynamically replace a DocType's class methods with your own custom functions at runtime.
This approach allows for deep customization of core logic, such as modifying the cancellation process of a Sales Invoice. However, it should be used with caution as it can have wide-ranging effects and may need updates if the underlying core code changes.
1 # In custom_app/custom_module/sales_invoice_override.py 2 import frappe 3 from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice 4
5 def shoutout(self): 6 print("Yay!") 7
8 def before_cancel(self): 9 self.shoutout() 10 # Note: The original 'update_time_sheet' method might not exist on all versions. 11 # This demonstrates calling another custom or existing method. 12 # In a real scenario, you might call the original method or implement new logic. 13 frappe.msgprint("Custom cancel logic executed!") 14
15 def apply_sales_invoice_patch(): 16 SalesInvoice.shoutout = shoutout 17 SalesInvoice.before_cancel = before_cancel
Understanding This Code
What It Does
This script dynamically adds a new method (`shoutout`) and overrides an existing method (`before_cancel`) on the standard `SalesInvoice` DocType class. All Sales Invoice document instances will use this new logic globally after the patch is applied.
When To Use
Use this technique when you need to change the fundamental behavior of a core DocType method that doesn't have a suitable server-side hook. It's ideal for injecting custom validation, logic, or integrations directly into a DocType's workflow.
Prerequisites
- •A custom Frappe app must be created.
- •Working knowledge of Python classes and functions.
- •Bench command-line interface for restarting the server.
Key Concepts
Important ideas to understand in this code
Monkey Patching
Monkey patching is the process of dynamically modifying classes or modules at runtime. In this context, we are not editing the ERPNext source code file, but instead assigning our custom functions to the `SalesInvoice` class in memory after it has been loaded.
Learn moreDocType Classes
Every DocType in Frappe is backed by a Python class. For the 'Sales Invoice' DocType, the class is `SalesInvoice`. By modifying this class, we change the behavior of all documents of that type.
Learn moreExecution via Hooks
For a patch to be effective, it must be executed when the Frappe application starts. This is typically done by calling the patch function (e.g., `apply_sales_invoice_patch`) from a startup hook like `on_startup` in your custom app's `hooks.py` file.
Learn moreStep-by-Step Tutorial
Follow along to understand how this code works
Create the Python File
Inside your custom Frappe app, create a new Python file. A good practice is to organize such overrides in a dedicated module, for example: `my_app/overrides/sales_invoice.py`.
# my_app/overrides/sales_invoice.py
# ... your code will go hereDefine Custom Methods
Write the Python functions that will serve as your new or replacement methods. The first argument must always be `self`, which refers to the document instance.
import frappe
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
def shoutout(self):
print("Yay!")
def before_cancel(self):
self.shoutout()
frappe.msgprint("Custom cancel logic executed!")Create the Patching Function
Create a wrapper function that performs the assignment of your custom methods to the DocType class. This keeps the patching logic organized.
def apply_sales_invoice_patch():
SalesInvoice.shoutout = shoutout
SalesInvoice.before_cancel = before_cancelExecute the Patch via Hooks
To ensure your patch is applied every time the server starts, you must call the patching function from your app's `hooks.py` file using the `on_startup` hook.
# my_app/hooks.py
on_startup = "my_app.overrides.sales_invoice.apply_sales_invoice_patch"Restart the Bench
Server-side Python code changes are not loaded automatically. You must restart the bench for the new hooks and code to take effect.
bench restartCommon Issues & Solutions
Troubleshoot problems you might encounter