How do I read the contents of a File?

I’m trying to write a custom data import. Currently, I have a button that pops up a dialog:

I’m now stuck in implementation. This is my JS code:

listview.page.add_inner_button('Import Members', function () {
    frappe.prompt({
        label: 'Attach a CSV file',
        fieldname: 'import_file',
        fieldtype: 'Attach'
    }, (values) => {
        frappe.call({
            method: 'custom_features.custom_features.doctype.campaign_member.campaign_member.import_members',
            args: {
                'url': values.import_file
            }
        });
    });
});

My import_members function is like this:

@frappe.whitelist()
def import_members(url):
    # My code

Now, I first need to read the file before starting on the importing. How do I do that? I’ve tried:

  • frappe.db.get("File", {"file_url": url}).get_contents() - this raises an error saying that NoneType isn’t callable, which means that the get_contents method is None.
  • requests.get(url) - to make this work, I prepended window.location to url before passing it here. But this raises a 403 error, although the URL works in the browser.

I don’t understand why both of the above methods don’t work. Especially the first one - it should either raise an error, or be a function, but it’s None.

Could you help me with getting the file contents? Possibly as a Pandas DF, but any format would work.

Thanks.

Any luck with this? I’m also looking into this. Shall post a solution here if I’m able to figure it out.

After I played around with the code I understood that using frappe.get_list returns a list of dictionaries. Only frappe.get_doc returns the actual document. The following code now works:

# You can only call `get_content` on a `frappe.get_doc` return
# and `frappe.get_doc` requires the name
name = frappe.db.get("File", {"file_url": url}).name
content = frappe.get_doc("File", name).get_content()

But this is a double line and unintuitive solution to something which must be fairly common - getting a list of documents.

Frappe is still very (very) young. Coming from Django, I find the ORM (and the docs, and the speed, and the bugs, and quite some other stuff) here painfully insufficient. For example, you can’t pass filters in many functions (.set_value and .get_doc are examples), you can only pass the PK. Which means I’ve got to do something like this:

doc = frappe.db.get("ToDo", {"field": "Some Value"})
# details is a big dictionary. It would be tiresome to set each key using `doc.key = value`
frappe.db.set_value("ToDo", doc.name, details) 

Sigh. But it has it’s advantages aspects, too. Hopefully will get good enough to contribute.