How to attach an existing file to a document

Thanks @peterg

It is created within the .py controller of the DocType. I have code to create a .csv file, which the user then has to download.

I can change the code to place the file anywhere I wish to, but thought it would be appropriate to place it in public/files

Hope that helps?

Manually placing files there without creating a File doc reference may lead to problems, though I don’t know enough about the File manager design to say for sure.

At the moment, I’m not really understanding the issue you’re facing. Frappe’s attachment system is pretty flexible, allowing attachment by upload or reference. What is it that’s currently happening that you want to avoid?

It’s highly probable that I do not have a clue as to how Attachments and the File DocType are related :rofl:

I’ve created a custom DocType with a custom button on the Form View

frappe.ui.form.on('Trace Request', {
	refresh(frm) {
		frm.add_custom_button('Export to Dolphin', () => {
			frappe.call({

My .py controller then creates a .csv file, containing data (not much), which is then to be opened in Excel by the user. All I need is for the user to be able to download the .csv file somehow. I cannot use a field on the DocType to contain the link to the file, which is easy to code, as the document on which the button is clicked is a Submitted document, and hence I cannot update any field. So I thought of “attaching” the file to the document so that a link to it will appear on the left sidebar under Attachments.

I have a current workaround, whereby I provide the link in a frappe.msgprint(), but this is a transient solution as the link is lost once the modal is closed.

Hope this is clearer now :thinking:

Attachments are instances of the File doctype, or at least that’s my understanding. It’s possible to add files directly to the public directory via python/shell/etc., but I’m not aware off-hand how actively Frappe tries to manage that directory’s contents. I’d be reluctant to add things there manually.

If you want to attach the file via an attach field, that’s probably the best bet. You can make it editable for submitted documents by ticking the “Allow on Submit” box.

Okay, I get that.

Then, is it possible to create a File DocType document with py code. In other words, not using the Upload dialog, but code in the custom DocType’s .py controller? Then I should be able to attach the File document to the Custom DocType document?

That means that my .py code would not place the file in the public directory, but the actual creation of the File document will place it there.

It pains me to see that this seems to be so difficult and so much underdocumented that a longterm forum participant like you seems to get frustrated to the point that you ask if this is even possible.
Why wouldn’t it?

I just tried the search on this forum with “attach file python” and found this which, depending on the precise situation of where your code-to-be needs to be located, might help you or not:

Best!

More info about reading the files back is in this thread, which also helps get a better picture of the data structures:

Or maybe this could help (upload via REST API):
https://frappeframework.com/docs/user/en/api/rest#file-uploads

It also links to this code for client side:

More suggestions in this thread, somewhat oldish so hopefully they still work, but may give insights:

This one from the same multi-year thread also looks promising and interesting:

Hello @Peer

Yip, frustrating it surely is :frowning_face:

I’ve searched extensively for this and have seen the posts your refer to.

file = frappe.get_doc("File", "4ab1a860dc")

The above refers to a File document with name/id 4ab1a860dc, and then attaches it to the Customer DocType. I want to upload a file programmatically so that it becomes a File document with a name/id similar to the above. Then I can attach it to my custom DocType just as was done above.

There seem to be many ways, anyway, depending on if you

  • are sitting in an app or in bench console and want to use the (a) Document API or the (b) Database API
  • talk to the backend as the frontend does (I saw some curl sending html form data), or
  • talk to the REST API’s doctype access, or
  • use some whitelisted method of the REST API.

Anyway, it seems to be a two step process:

  • Get the file to a place the backend normally uses and have it registered
  • Attach it to some document.
    Maybe these can be rolled into one.

There are many stubs in the forum here and there. I also spent some time some weeks ago and finally found clean help on stackoverflow, but didn’t push it till the end due to distraction, otherwise I’d have posted the code here. :-/

The use case is clear. For instance, I have another thingy containing 1.8k articles and their much more photos and it’s easy to get them out of it. But the upload to erpnext is less obvious. Who would want to upload and/or attach (re-link) all these manually?

Here is the backend’s python code of the file manager:

This may help in an app.

There also is 1 whitelisted method in line 398ff.:

Finding this:

I see that there is an API endpoint in line 377 like this:
xhr.open('POST', '/api/method/upload_file', true);
(followed by some header an CSRF security items, then building a form upload structure)
which betrays a whitelisted upload_file method which might be of use.
It seems to be just one “grep -r upload_file *” away from a usable point in the frappe app tree.

Here is another code snippet showing how to upload a file (using shell):

And we even got a long list of many other v14 api endpoints useable for many purposes:

but, well, a recursive grep might help in many cases, too.

Can’t you just set an attach field to the file location and let the doc controller handle all the reference stuff?

doc.my_attach_field = '/files/file.ext'
doc.save()

On quick testing at least, that seems to work for me.

2 Likes

That’s a little adventure in itself, but educative.
If you just do a $ grep -r upload_file you get a lot of lines.
You add a less: $ grep -r upload_file | less and it’s still long.
So you use the search in less with /upload_file and navigate with “n” and “p” but quickly get stuck and can’t use the terminal any more (windows terminal), which then you need to close. So the list is toxic for the terminal, because the long one-liner JS bundles break it.
You can workaround this by grepping like this: $ grep -r upload_file | nl | less, this line-numbers the list and you can approach the JS lines more carefully to examine the list.
This is no big deal for many experienced devs, but not everybody is.
If you are in the container you can’t “man” these commands because they are optimized for space, and the man pages apparently don’t qualify, understandably so.
Losing a terminal is an unwelcome hindrance, but so what, you kill the unresponsive terminal and just get a new one – re-logging into ssh, re-logging into the container with docker, re-finding your bench and app directory. Just normal code monkeyness.

So this “easy grep” finally takes us to:
frappe@504a3ceeae0a:~/frappe-bench/apps/frappe$ less frappe/handler.py
where you find the upload_file method, so you can see what exactly it does and where the files really ends up.

Bonus:
Did you know there also is a whitelisted uploadfile method, but apparently almost end-of-life (missing the date of v16 release, though :wink: )? See here (in “handler.py”):

@frappe.whitelist()
def uploadfile():
        deprecation_warning(
                "uploadfile is deprecated and will be removed in v16. Use upload_file instead.",
        )

Otherwise, there also is the needed:

@frappe.whitelist(allow_guest=True)
def upload_file():

Enjoy!

Thank you, @peterg, this seems to very well respond to the OP’s need.

How did you learn this?
Do you have a link to some documentation?

After all, you don’t seem to be sure what it really does, so I’d suggest that with due diligence, this also would need some more grepping and code reading/checking before use, wouldn’t it?

I think the hoops we need to jump through are due in part to canalizing the file upload along a path where crackers can’t easily abuse a door too wide open for XSS and similar nefarious purposes.

That said, the file “Upload” dialog contains some almost-lies: files referenced by “Links” are NOT uploaded as one would expect, only the link itself. This could get a site or its admins into trouble if the referenced site disappears, and some other sites might even post a “image stolen from …” image to discourage deep linking and bandwidth hogs.

Also, it doesn’t say how files can be put into the “Library”.

This resembles the question of the OP, but he got it there by his own (thus trusted) path.

The REST API documentation has some info about file uploads:
https://frappeframework.com/docs/user/en/api/rest#file-uploads
But really, who will use curl for mass file uploads? You could, but who doesn’t like some python example?

Also it links to JS code useable in a JS client script. Why not.

But why not also link to the python implementation code in the handler?
And ideally supply a piece of python code to link to it?

Is the doc only meant for experienced devs and beginner users, but not for those in-between?
Is this policy, or more like a by-product of agile development of a team dedicated to unicorn-building-chasing?

But I’d suggest, once again, that mass adoption probably comes from including the many devs, not just the ahead of the curve-devs building MVPs – very polished ones, that is, indeed – and then catching up.

But the many also need to imply themselves documenting stuff. This forum could help
more with quality How-tos, as some forum members already build.

Which is why I write these longish and verbose explanations of finding your way around in The Thing.

I’m not sure it’s really all that complicated. This is what the attach field was designed to do, isn’t it? It seems to work fine as far as I can tell. Everyone’s mileage may vary, of course.

I don’t know.

But you write this from the perspective of an app developer, who is content if it works even without being sure what it does.

If you just want to connect to the API, it’s different. In principle, with clean doc in sync with the code, you’d have the “contract” of the API to code for and know the details, without dark corners.
But that doc doesn’t exist, so you end up erring around in the forum and the web, and if you want to know what you do, you read lots-n-lots of code. That’s ok, but much slower, and you don’t really need the API any more. So the API is of reduced usability due to lack of documenting it properly.

How many people get put off by this situation, and turn to other solutions?
I don’t know, maybe not many – as you suggest – but maybe more than we’d like think?

1 Like

Thanks so much @Peer and @peterg

Yes, it is beyond frustrating to fumble along and spend hours searching this forum for titbits of both related and unrelated content which might shed light on what it is you’re looking for, or how to achieve something.

Nevertheless, this is an old issue of having poor to almost non-existent documentation, bar for the most obvious useage, and it turns out to be a catch 22: Unless if you’re a developer you cannot read the code to a sufficient level to compile documentation. And since it’s the expressed objective of the developers to chase features rather than properly finishing off existing code with thorough user acceptance testing, debugging and documentation, they rather move on to the next “feature”. The terms “feature fatigue”, “frappe framework” and “f…g frustrating” has become synonymous and all 3 referred to by the same acronym: FF.

I could be completely wrong, however I’ve been on this forum for long enough to have heard the same sentiment echoed over and over…

I can’t say I understand the negativity here.

The goal was to attach a file to a doctype. The solution was to use an Attach field, which is designed and documented for that purpose. Seems pretty straight forward to me.

Granted.

This was in response to @Peer commenting on the lack of proper documentation in support of the average power user, not seasoned developer, wanting to get into their system. I’ve spent the last 3 days trying to figure out why an email template does not want to render, notwithstanding following better than average documentation to the tee. Searching for clues on this forum returns at least 10 similar cries for assistance, spanning many years, all of which un-resolved. So it took me 3 days to figure out why the template does not render, all of which could so easily have been avoided, not only for me, but countless other power-users who have battled with the same issue.

I accept that attaching a file to a document from a backend .py script is not your average use-case, but the lack of documentation in this regard has the same impact as the incomplete documentation regarding email templates. It is frustratingly difficult for a power user to use Frappe.

Maybe you could take a look at the recently added deprecation_dumpster, the intro from @blaggacao might indeed brighten everybody up as stated:

devx: add deprecation dumpster (#27887) · frappe/frappe@8cfeb15 · GitHub

Nice addition!

There you’ll even get a glimpse into upcoming changes for v17.

Found by skimming through the history of the handler.py file. Some code archeology helps to position yourself and understand where the developer ecosystem is coming from. This code also has its history which is interesting to understand how and why it eveolves in this or that direction. It’s even part of history in general, as it’s part of the search of most of us of how to live in a society (or sub-society) of people strongly driven by goodness and sharing in a situation of people with unusually strong attachment to freedom.

Anyhow, thanks so much @peterg and @Peer for your assistance in helping me with what this topic was all about form the outset : to attach a file from a backend .py script without uploading it first to create a File document. Your assistance is much appreciated and without knowledgeable forum members like yourselves this forum would in essence be nothing other than a wailing wall.