Skip to content

Instantly share code, notes, and snippets.

@revant
Last active April 18, 2025 00:30
Show Gist options
  • Save revant/1a4bd20a031ed0158a9a7a3d4eacb92d to your computer and use it in GitHub Desktop.
Save revant/1a4bd20a031ed0158a9a7a3d4eacb92d to your computer and use it in GitHub Desktop.
Multi Part Upload for Frappe
// Copyright (c) 2025, Revant Nandgaonkar and contributors
// License: MIT
// HTML Field has following options
// <div id="dropzoneTarget" class="fallback dropzone"> <input hidden name="file" type="file"> </div>
frappe.ui.form.on('File Uploader', {
refresh(frm) {
const url = '/api/method/multipart_upload.upload.multipart_file_upload';
const chunkSize = 20 * 1024 * 1024; // 20MB
new Dropzone('div#dropzoneTarget', {
url,
paramName: 'file',
chunking: true,
forceChunking: true,
maxFilesize: 10 * 1024, // 10GB
timeout: 120000, // 2 minutes,
chunkSize, // bytes
headers: {
'X-Frappe-CSRF-Token': frappe.csrf_token,
},
params: (files, xhr, chunk) => {
if (chunk) {
return {
dzchunkindex: chunk.index,
dzchunkbyteoffset: chunk.index * chunkSize,
dztotalchunkcount: chunk.file.upload.totalChunkCount,
dztotalfilesize: chunk.file.size,
// fieldname: "fieldname",
folder: 'Attachments',
doctype: frm.doc.doctype,
docname: frm.doc.name,
is_private: frm.doc.is_private,
};
}
},
});
},
});
# Copyright (c) 2025, Revant Nandgaonkar and contributors
# License: MIT
# ...
app_include_css = ["https://unpkg.com/dropzone@5/dist/min/dropzone.min.css"]
app_include_js = ["https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"]
# Copyright (c) 2025, Revant Nandgaonkar and contributors
# License: MIT
# references:
# https://stackoverflow.com/a/53001103
# https://github.com/frappe/drive/blob/main/frontend/src/components/FileUploader.vue
# https://github.com/frappe/frappe/blob/develop/frappe/client.py
import os
import frappe
from frappe.utils import cint
from werkzeug.utils import secure_filename
class FileAlreadyExistsException(Exception):
http_status_code = 400
class InternalServerException(Exception):
http_status_code = 500
@frappe.whitelist(methods=["POST"])
def multipart_file_upload(*args, **kwargs):
file = frappe.request.files["file"]
filename = secure_filename(file.filename)
is_private = frappe.form_dict.is_private or 1
file_dir = "private/files" if is_private else "public/files"
file_url = os.path.join(
"/private/files" if is_private else "/files",
filename,
)
save_path = os.path.join(frappe.get_site_path(file_dir), filename)
current_chunk = int(frappe.form_dict.dzchunkindex)
if os.path.exists(save_path) and current_chunk == 0:
return frappe.throw(
"File already exists",
exc=FileAlreadyExistsException,
)
try:
with open(save_path, "ab") as f:
f.seek(int(frappe.form_dict.dzchunkbyteoffset))
f.write(file.stream.read())
except OSError as e:
frappe.log_error("Could not write to file", e)
return frappe.throw(
("Not sure why, but we couldn't write the file to disk"),
exc=InternalServerException,
)
total_chunks = int(frappe.form_dict.dztotalchunkcount)
if current_chunk + 1 == total_chunks:
if os.path.getsize(save_path) != int(frappe.form_dict.dztotalfilesize):
frappe.log_error(
f"File {filename} was completed, "
f"but has a size mismatch."
f"Was {os.path.getsize(save_path)} but we"
f" expected {frappe.form_dict.dztotalfilesize} "
)
# delete incomplete file
os.remove(save_path)
return frappe.throw("Size mismatch", exc=InternalServerException)
else:
frappe.log(f"File {filename} has been uploaded successfully")
else:
frappe.log(
(
f"Chunk {current_chunk + 1} of "
f"{total_chunks} for file {filename} complete"
)
)
doctype = frappe.form_dict.doctype
docname = frappe.form_dict.docname
fieldname = frappe.form_dict.fieldname
folder = frappe.form_dict.folder or "Home"
frappe_file = frappe.get_doc(
{
"doctype": "File",
"attached_to_doctype": doctype,
"attached_to_name": docname,
"attached_to_field": fieldname,
"folder": folder,
"file_name": filename,
"file_url": file_url,
"is_private": cint(is_private),
}
)
frappe_file.save(ignore_permissions=True)
return "Chunk upload successful"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment