Understanding differences between frm.call() and frappe.call()

Hi all!

I’ve been working on a custom app, and I’ve encountered an issue that I would appreciate your insights on.

In my Javascript code, I am using frm.call() to call an ERPNext server-side Python method. For example:

frm.call('establish_connection_to_marketplace_API');

This works perfectly. On the backend (I’m using ERPNext development container) I modified the frm.call() function to log the actual path that it uses to resolve the Python method, and it revealed that the path is “marketplace_integration.marketplace_integration.doctype.marketplace_settings.marketplace_settings.establish_connection_to_marketplace_API”. It makes sense, as the local path for the file is /workspace/development/frappe-bench/apps/marketplace_integration/marketplace_integration/marketplace_integration/doctype/marketplace_settings/marketplace_settings.py

However, when I try to accomplish the same using frappe.call(), like below, it fails:

frappe.call({
    method: 'marketplace_integration.marketplace_integration.doctype.marketplace_settings.marketplace_settings.establish_connection_to_marketplace_API',
});

The frappe.call() results in an error message:

“Failed to get method for command marketplace_integration.marketplace_integration.doctype.marketplace_settings.marketplace_settings.establish_connection_to_marketplace_API’ with module ‘marketplace_integration.marketplace_integration.doctype.marketplace_settings.marketplace_settings’ has no attribute ‘establish_connection_to_marketplace_API’”

I’m sooooooo confused :frowning: In other JS files I need to use frappe.call (e.g. listview) and I don’t know what is wrong. Of course python function is whitelisted:

class MarketplaceSettings(Document):
    
    @frappe.whitelist()
    def establish_connection_to_marketplace_API(self):

Any smart people who know what is wrong? :< Thank you in advance for your assistance.

frm.call("establish_connection_to_marketplace_API") is doing this under the hood:

frappe.call({
	doc: frm.doc,
	method: "establish_connection_to_marketplace_API",
});

When we send a doc argument, the backend will instantiate the document and call the method on it.

When we send no doc argument, the backend will execute a static method, which must not be defined within the document class.

In you case it would need to be defined as:

class MarketplaceSettings(Document):
    pass

@frappe.whitelist()
def establish_connection_to_marketplace_API():
    pass

Notice how the method is defined outside the class and without a self argument

7 Likes

frm.call() takes advantage of the fact that each doctype has its own .js and .py files. So it can directly call a method within the .py file if the call is written in the .js file.
This also allows the inheritance of the class (self) within the method.

As for frappe.call() it works more like an api call to a method in any .py file in the app. This requires the method to be outside of a document class. (Not entirely sure on the why though.)

4 Likes

Thank you guys - counter-intuitive but it solved the problem!