diff --git a/frappe/app.py b/frappe/app.py
index 8e1534e7ef..70575fe2f1 100644
--- a/frappe/app.py
+++ b/frappe/app.py
@@ -120,6 +120,8 @@ def init_request(request):
else:
frappe.connect(set_admin_as_user=False)
+ request.max_content_length = frappe.local.conf.get('max_file_size') or 10 * 1024 * 1024
+
make_form_dict(request)
if request.method != "OPTIONS":
diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py
index 4df9ef3132..0021240106 100755
--- a/frappe/core/doctype/file/file.py
+++ b/frappe/core/doctype/file/file.py
@@ -716,13 +716,11 @@ def delete_file(path):
os.remove(path)
-
-
+@frappe.whitelist()
def get_max_file_size():
return cint(conf.get('max_file_size')) or 10485760
-
def has_permission(doc, ptype=None, user=None):
has_access = False
user = user or frappe.session.user
diff --git a/frappe/public/js/frappe/file_uploader/FilePreview.vue b/frappe/public/js/frappe/file_uploader/FilePreview.vue
index 43dbacb17d..5972a975f2 100644
--- a/frappe/public/js/frappe/file_uploader/FilePreview.vue
+++ b/frappe/public/js/frappe/file_uploader/FilePreview.vue
@@ -29,21 +29,26 @@
+
+
+ {{ file.error_message }}
+
+
@@ -89,18 +94,18 @@ export default {
return this.file.doc ? this.file.doc.is_private : this.file.private;
},
uploaded() {
- return this.file.total && this.file.total === this.file.progress && !this.file.failed;
+ return this.file.request_succeeded;
},
is_image() {
return this.file.file_obj.type.startsWith('image');
},
is_optimizable() {
let is_svg = this.file.file_obj.type == 'image/svg+xml';
- return this.is_image && !is_svg;
+ return this.is_image && !is_svg && !this.uploaded && !this.file.failed;
},
is_cropable() {
let croppable_types = ['image/jpeg', 'image/png'];
- return !this.uploaded && !this.file.uploading && croppable_types.includes(this.file.file_obj.type);
+ return !this.uploaded && !this.file.uploading && !this.file.failed && croppable_types.includes(this.file.file_obj.type);
},
progress() {
let value = Math.round((this.file.progress * 100) / this.file.total);
@@ -208,4 +213,9 @@ export default {
align-items: center;
padding-top: 0.25rem;
}
+
+.file-error {
+ font-size: var(--text-sm);
+ font-weight: var(--text-bold);
+}
diff --git a/frappe/public/js/frappe/file_uploader/FileUploader.vue b/frappe/public/js/frappe/file_uploader/FileUploader.vue
index 90aa545941..167b4955fa 100644
--- a/frappe/public/js/frappe/file_uploader/FileUploader.vue
+++ b/frappe/public/js/frappe/file_uploader/FileUploader.vue
@@ -197,6 +197,7 @@ export default {
show_image_cropper: false,
crop_image_with_index: -1,
trigger_upload: false,
+ close_dialog: false,
hide_dialog_footer: false,
allow_take_photo: false,
allow_web_link: true,
@@ -218,6 +219,12 @@ export default {
}
});
}
+ if (this.restrictions.max_file_size == null) {
+ frappe.call('frappe.core.doctype.file.file.get_max_file_size')
+ .then(res => {
+ this.restrictions.max_file_size = Number(res.message);
+ });
+ }
},
watch: {
files(newvalue, oldvalue) {
@@ -289,6 +296,8 @@ export default {
progress: 0,
total: 0,
failed: false,
+ request_succeeded: false,
+ error_message: null,
uploading: false,
private: !is_image
}
@@ -329,9 +338,17 @@ export default {
if (!is_correct_type) {
console.warn('File skipped because of invalid file type', file);
+ frappe.show_alert({
+ message: __('File "{0}" was skipped because of invalid file type', [file.name]),
+ indicator: 'orange'
+ });
}
if (!valid_file_size) {
console.warn('File skipped because of invalid file size', file.size, file);
+ frappe.show_alert({
+ message: __('File "{0}" was skipped because size exceeds {1} MB', [file.name, max_file_size / (1024 * 1024)]),
+ indicator: 'orange'
+ });
}
return is_correct_type && valid_file_size;
@@ -357,9 +374,10 @@ export default {
let selected_file = this.$refs.file_browser.selected_node;
if (!selected_file.value) {
frappe.msgprint(__('Click on a file to select it.'));
+ this.close_dialog = true;
return Promise.reject();
}
-
+ this.close_dialog = true;
return this.upload_file({
file_url: selected_file.file_url
});
@@ -368,9 +386,11 @@ export default {
let file_url = this.$refs.web_link.url;
if (!file_url) {
frappe.msgprint(__('Invalid URL'));
+ this.close_dialog = true;
return Promise.reject();
}
file_url = decodeURI(file_url)
+ this.close_dialog = true;
return this.upload_file({
file_url
});
@@ -383,6 +403,7 @@ export default {
this.on_success && this.on_success(file);
})
);
+ this.close_dialog = true;
return Promise.all(promises);
},
upload_file(file, i) {
@@ -410,6 +431,7 @@ export default {
xhr.onreadystatechange = () => {
if (xhr.readyState == XMLHttpRequest.DONE) {
if (xhr.status === 200) {
+ file.request_succeeded = true;
let r = null;
let file_doc = null;
try {
@@ -426,15 +448,24 @@ export default {
if (this.on_success) {
this.on_success(file_doc, r);
}
+
+ if (i == this.files.length - 1 && this.files.every(file => file.request_succeeded)) {
+ this.close_dialog = true;
+ }
+
} else if (xhr.status === 403) {
+ file.failed = true;
let response = JSON.parse(xhr.responseText);
- frappe.msgprint({
- title: __('Not permitted'),
- indicator: 'red',
- message: response._error_message
- });
+ file.error_message = `Not permitted. ${response._error_message || ''}`;
+
+ } else if (xhr.status === 413) {
+ file.failed = true;
+ file.error_message = 'Size exceeds the maximum allowed file size.';
+
} else {
file.failed = true;
+ file.error_message = xhr.status === 0 ? 'XMLHttpRequest Error' : `${xhr.status} : ${xhr.statusText}`;
+
let error = null;
try {
error = JSON.parse(xhr.responseText);
diff --git a/frappe/public/js/frappe/file_uploader/index.js b/frappe/public/js/frappe/file_uploader/index.js
index 87bc1c8ec8..ec90b19a1a 100644
--- a/frappe/public/js/frappe/file_uploader/index.js
+++ b/frappe/public/js/frappe/file_uploader/index.js
@@ -67,6 +67,12 @@ export default class FileUploader {
}
});
+ this.uploader.$watch('close_dialog', (close_dialog) => {
+ if (close_dialog) {
+ this.dialog && this.dialog.hide();
+ }
+ });
+
this.uploader.$watch('hide_dialog_footer', (hide_dialog_footer) => {
if (hide_dialog_footer) {
this.dialog && this.dialog.footer.addClass('hide');
@@ -84,10 +90,8 @@ export default class FileUploader {
upload_files() {
this.dialog && this.dialog.get_primary_btn().prop('disabled', true);
- return this.uploader.upload_files()
- .then(() => {
- this.dialog && this.dialog.hide();
- });
+ this.dialog && this.dialog.get_secondary_btn().prop('disabled', true);
+ return this.uploader.upload_files();
}
make_dialog() {