diff --git a/.travis.yml b/.travis.yml index bae95835e3..1c4e8acbc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,6 @@ install: - sudo apt-get purge -y mysql-common - wget https://raw.githubusercontent.com/frappe/bench/master/install_scripts/setup_frappe.sh - sudo bash setup_frappe.sh --skip-setup-bench --mysql-root-password travis - - sudo pip install --upgrade pip - rm $TRAVIS_BUILD_DIR/.git/shallow - cd ~/ && bench init frappe-bench --frappe-path $TRAVIS_BUILD_DIR - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ diff --git a/frappe/app.py b/frappe/app.py index f1bbb28519..af3a489e01 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -72,6 +72,9 @@ def application(request): elif frappe.request.path.startswith('/backups'): response = frappe.utils.response.download_backup(request.path) + elif frappe.request.path.startswith('/private/files/'): + response = frappe.utils.response.download_private_file(request.path) + elif frappe.local.request.method in ('GET', 'HEAD'): response = frappe.website.render.render(request.path) diff --git a/frappe/change_log/current/private_files.md b/frappe/change_log/current/private_files.md new file mode 100644 index 0000000000..661e04f8b4 --- /dev/null +++ b/frappe/change_log/current/private_files.md @@ -0,0 +1,4 @@ +- Attachments can now be marked as **Private** + - Private files cannot be accessed unless you are logged in + - To access a private file, you need to have read permission on the file or read permission on the document to which the file is attached + - All attachments in a new incoming email are private diff --git a/frappe/commands.py b/frappe/commands.py index cf2ebec80a..784efdb64c 100644 --- a/frappe/commands.py +++ b/frappe/commands.py @@ -868,21 +868,23 @@ def use(site, sites_path='.'): @click.command('backup') @click.option('--with-files', default=False, is_flag=True, help="Take backup with files") @pass_context -def backup(context, with_files=False, backup_path_db=None, backup_path_files=None, quiet=False): +def backup(context, with_files=False, backup_path_db=None, backup_path_files=None, + backup_path_private_files=None, quiet=False): "Backup" from frappe.utils.backups import scheduled_backup verbose = context.verbose for site in context.sites: frappe.init(site=site) frappe.connect() - odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, force=True) + odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, backup_path_private_files=backup_path_private_files, force=True) if verbose: from frappe.utils import now print "database backup taken -", odb.backup_path_db, "- on", now() if with_files: print "files backup taken -", odb.backup_path_files, "- on", now() - frappe.destroy() + print "private files backup taken -", odb.backup_path_private_files, "- on", now() + frappe.destroy() @click.command('remove-from-installed-apps') @click.argument('app') diff --git a/frappe/core/doctype/file/file.js b/frappe/core/doctype/file/file.js index 7f05e2ff9a..bdcd6cf119 100644 --- a/frappe/core/doctype/file/file.js +++ b/frappe/core/doctype/file/file.js @@ -1,7 +1,11 @@ frappe.ui.form.on("File", "refresh", function(frm) { if(!frm.doc.is_folder) { frm.add_custom_button(__('Download'), function() { - window.open(frm.doc.file_url); + var file_url = frm.doc.file_url; + if (frm.doc.file_name) { + file_url = file_url.replace(/#/g, '%23'); + } + window.open(file_url); }, "icon-download"); } diff --git a/frappe/core/doctype/file/file.json b/frappe/core/doctype/file/file.json index f561fee71d..7c7b2a0a2a 100644 --- a/frappe/core/doctype/file/file.json +++ b/frappe/core/doctype/file/file.json @@ -25,6 +25,7 @@ "oldfieldtype": "Data", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -32,6 +33,31 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "eval:!doc.is_folder", + "fieldname": "is_private", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Is Private", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 1, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -48,6 +74,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -71,6 +98,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -93,6 +121,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -117,6 +146,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -140,6 +170,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -162,6 +193,7 @@ "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -184,6 +216,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -207,6 +240,7 @@ "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -230,6 +264,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -254,6 +289,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -277,6 +313,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -300,6 +337,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -323,6 +361,7 @@ "options": "DocType", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -345,6 +384,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -367,6 +407,7 @@ "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, "reqd": 0, @@ -389,6 +430,7 @@ "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -412,6 +454,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -435,6 +478,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -458,6 +502,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -476,7 +521,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2015-11-16 06:29:47.191098", + "menu_index": 0, + "modified": "2015-12-08 05:03:48.767257", "modified_by": "Administrator", "module": "Core", "name": "File", diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 4391f76296..f32caa370b 100644 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -108,6 +108,9 @@ class File(NestedSet): frappe.throw(_("Folder is mandatory")) def validate_file(self): + """Validates existence of public file + TODO: validate for private file + """ if (self.file_url or "").startswith("/files/"): if not self.file_name: self.file_name = self.file_url.split("/files/")[-1] @@ -337,3 +340,12 @@ def get_web_image(file_url): filename = "/files/" + strip(urllib.unquote(filename)) return image, filename, extn + +def check_file_permission(file_url): + for file in frappe.get_all("File", filters={"file_url": file_url, "is_private": 1}, fields=["name", "attached_to_doctype", "attached_to_name"]): + + if (frappe.has_permission("File", ptype="read", doc=file.name) + or frappe.has_permission(file.attached_to_doctype, ptype="read", doc=file.attached_to_name)): + return True + + raise frappe.PermissionError diff --git a/frappe/core/doctype/file/file_list.js b/frappe/core/doctype/file/file_list.js index 20b860fa7d..536a28b9a2 100644 --- a/frappe/core/doctype/file/file_list.js +++ b/frappe/core/doctype/file/file_list.js @@ -3,7 +3,7 @@ frappe.provide("frappe.ui"); frappe.listview_settings['File'] = { hide_name_column: true, use_route: true, - add_fields: ["is_folder", "file_name", "file_url", "folder"], + add_fields: ["is_folder", "file_name", "file_url", "folder", "is_private"], formatters: { file_size: function(value) { // formatter for file size @@ -17,13 +17,20 @@ frappe.listview_settings['File'] = { }, prepare_data: function(data) { // set image icons + var icon = "" + if(data.is_folder) { - data._title = ' ' + data.file_name; + icon += ' '; } else if(frappe.utils.is_image_file(data.file_name)) { - data._title = ' ' + data.file_name; + icon += ' '; } else { - data._title = ' \ - ' + (data.file_name ? data.file_name : data.file_url); + icon += ' ' + } + + data._title = icon + (data.file_name ? data.file_name : data.file_url) + + if (data.is_private) { + data._title += ' ' } }, onload: function(doclist) { @@ -87,7 +94,9 @@ frappe.listview_settings['File'] = { frappe.upload.upload_file(dataTransfer.files[0], { "folder": doclist.current_folder, "from_form": 1 - }, {}); + }, { + confirm_is_private: 1 + }); }); }, add_menu_item_copy: function(doclist){ diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 3c82f0c07c..2dd1c2d1a5 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -100,7 +100,7 @@ def get_user_permissions(meta): return out def get_attachments(dt, dn): - return frappe.get_all("File", fields=["name", "file_name", "file_url"], + return frappe.get_all("File", fields=["name", "file_name", "file_url", "is_private"], filters = {"attached_to_name": dn, "attached_to_doctype": dt}) def get_comments(dt, dn, limit=100): @@ -121,11 +121,11 @@ def get_comments(dt, dn, limit=100): as_dict=True) for c in communications: - c.attachments = json.dumps([f.file_url for f in frappe.get_all("File", - fields=["file_url"], + c.attachments = json.dumps(frappe.get_all("File", + fields=["file_url", "is_private"], filters={"attached_to_doctype": "Communication", "attached_to_name": c.name} - )]) + )) return comments + communications diff --git a/frappe/desk/page/backups/backups.py b/frappe/desk/page/backups/backups.py index 9f798f6841..0cf31c5aa3 100644 --- a/frappe/desk/page/backups/backups.py +++ b/frappe/desk/page/backups/backups.py @@ -20,5 +20,6 @@ def get_context(context): files = [('/backups/' + _file, get_time(os.path.join(path, _file)), get_size(os.path.join(path, _file))) for _file in files] + files.sort(key=lambda x: x[1], reverse=True) return {"files": files} diff --git a/frappe/docs/current/api/utils/frappe.utils.backups.html b/frappe/docs/current/api/utils/frappe.utils.backups.html index 0663d9c92e..1b05924bcf 100644 --- a/frappe/docs/current/api/utils/frappe.utils.backups.html +++ b/frappe/docs/current/api/utils/frappe.utils.backups.html @@ -32,7 +32,7 @@ If specifying dbfilename, also append ".sql.gz"

__init__ - (self, db_name, user, password, backup_path_db=None, backup_path_files=None, db_host=localhost) + (self, db_name, user, password, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, db_host=localhost)

No docs

@@ -223,7 +223,7 @@ False: file is new

frappe.utils.backups.new_backup - (older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, force=False) + (older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, force=False)

No docs

@@ -239,7 +239,7 @@ False: file is new

frappe.utils.backups.scheduled_backup - (older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, force=False) + (older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, force=False)

this function is called from scheduler deletes backups older than 7 days diff --git a/frappe/docs/current/api/utils/frappe.utils.file_manager.html b/frappe/docs/current/api/utils/frappe.utils.file_manager.html index f96efd57fc..5cce4577d6 100644 --- a/frappe/docs/current/api/utils/frappe.utils.file_manager.html +++ b/frappe/docs/current/api/utils/frappe.utils.file_manager.html @@ -164,7 +164,7 @@ frappe.utils.file_manager.get_file_data_from_hash - (content_hash) + (content_hash, is_private=0)

No docs

@@ -308,7 +308,7 @@ frappe.utils.file_manager.save_file - (fname, content, dt, dn, folder=None, decode=False) + (fname, content, dt, dn, folder=None, decode=False, is_private=0)

No docs

@@ -324,7 +324,7 @@ frappe.utils.file_manager.save_file_on_filesystem - (fname, content, content_type=None) + (fname, content, content_type=None, is_private=0)

No docs

@@ -340,7 +340,7 @@ frappe.utils.file_manager.save_uploaded - (dt, dn, folder) + (dt, dn, folder, is_private)

No docs

@@ -388,7 +388,7 @@ frappe.utils.file_manager.write_file - (content, file_path, fname) + (content, fname, is_private=0)

write file to disk with a random name (to compare)

diff --git a/frappe/docs/current/api/utils/frappe.utils.response.html b/frappe/docs/current/api/utils/frappe.utils.response.html index 85d71320ea..ebf1652825 100644 --- a/frappe/docs/current/api/utils/frappe.utils.response.html +++ b/frappe/docs/current/api/utils/frappe.utils.response.html @@ -113,6 +113,22 @@ +

+ + + frappe.utils.response.download_private_file + (path) +

+

Checks permissions and sends back private file

+
+
+ + + + + + +

diff --git a/frappe/docs/current/index.html b/frappe/docs/current/index.html index f29a91858d..2d32403343 100644 --- a/frappe/docs/current/index.html +++ b/frappe/docs/current/index.html @@ -35,7 +35,7 @@ Version - 6.12.3 + 6.12.4 diff --git a/frappe/docs/current/models/core/file.html b/frappe/docs/current/models/core/file.html index cc0c0b2542..d35fcab65b 100644 --- a/frappe/docs/current/models/core/file.html +++ b/frappe/docs/current/models/core/file.html @@ -50,8 +50,20 @@ - + 2 + is_private + + Check + + Is Private + + + + + + + 3 preview Section Break @@ -63,7 +75,7 @@ - 3 + 4 preview_html HTML @@ -75,7 +87,7 @@ - 4 + 5 section_break_5 Section Break @@ -87,7 +99,7 @@ - 5 + 6 is_home_folder Check @@ -99,7 +111,7 @@ - 6 + 7 is_attachments_folder Check @@ -111,7 +123,7 @@ - 7 + 8 file_size Int @@ -123,7 +135,7 @@ - 8 + 9 column_break_5 Column Break @@ -135,7 +147,7 @@ - 9 + 10 file_url Small Text @@ -147,7 +159,7 @@ - 10 + 11 thumbnail_url Small Text @@ -159,7 +171,7 @@ - 11 + 12 folder Link @@ -180,7 +192,7 @@ - 12 + 13 is_folder Check @@ -192,7 +204,7 @@ - 13 + 14 section_break_8 Section Break @@ -204,7 +216,7 @@ - 14 + 15 attached_to_doctype Link @@ -225,7 +237,7 @@ - 15 + 16 column_break_10 Column Break @@ -237,7 +249,7 @@ - 16 + 17 attached_to_name Data @@ -249,7 +261,7 @@ - 17 + 18 content_hash Data @@ -261,7 +273,7 @@ - 18 + 19 lft Int @@ -273,7 +285,7 @@ - 19 + 20 rgt Int @@ -285,7 +297,7 @@ - 20 + 21 old_parent Data @@ -607,7 +619,8 @@ validate_file (self)

-

No docs

+

Validates existence of public file +TODO: validate for private file


@@ -649,6 +662,22 @@ + +

+ + + frappe.core.doctype.file.file.check_file_permission + (file_url) +

+

No docs

+
+
+ + + + + +

Public API
/api/method/frappe.core.doctype.file.file.create_new_folder

diff --git a/frappe/docs/current/models/integrations/dropbox_backup.html b/frappe/docs/current/models/integrations/dropbox_backup.html index d025bbe040..fc12ae708e 100644 --- a/frappe/docs/current/models/integrations/dropbox_backup.html +++ b/frappe/docs/current/models/integrations/dropbox_backup.html @@ -316,6 +316,22 @@ Weekly + + + + +

+ + + frappe.integrations.doctype.dropbox_backup.dropbox_backup.upload_from_folder + (path, dropbox_folder, dropbox_client, did_not_upload, error_log) +

+

No docs

+
+
+ + + diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 4eb2de27f6..af87158152 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -355,7 +355,7 @@ class Email: for attachment in self.attachments: try: file_data = save_file(attachment['fname'], attachment['fcontent'], - doc.doctype, doc.name) + doc.doctype, doc.name, is_private=1) saved_attachments.append(file_data) if attachment['fname'] in self.cid_map: diff --git a/frappe/integrations/doctype/dropbox_backup/dropbox_backup.py b/frappe/integrations/doctype/dropbox_backup/dropbox_backup.py index fabc792301..df6bd20422 100644 --- a/frappe/integrations/doctype/dropbox_backup/dropbox_backup.py +++ b/frappe/integrations/doctype/dropbox_backup/dropbox_backup.py @@ -102,10 +102,12 @@ def dropbox_callback(oauth_token=None, not_approved=False): frappe.db.set_value("Dropbox Backup", "Dropbox Backup", "dropbox_access_allowed", allowed) frappe.db.set_value("Dropbox Backup", "Dropbox Backup", "send_backups_to_dropbox", 1) dropbox_client = client.DropboxClient(sess) - try: - dropbox_client.file_create_folder("files") - except: - pass + # try: + # dropbox_client.file_create_folder("private") + # dropbox_client.file_create_folder("private/files") + # dropbox_client.file_create_folder("files") + # except: + # pass else: allowed = 0 @@ -144,12 +146,27 @@ def backup_to_dropbox(): upload_file_to_dropbox(filename, "/database", dropbox_client) frappe.db.close() - response = dropbox_client.metadata("/files") # upload files to files folder did_not_upload = [] error_log = [] - path = get_files_path() + + upload_from_folder(get_files_path(), "/files", dropbox_client, did_not_upload, error_log) + upload_from_folder(get_files_path(is_private=1), "/private/files", dropbox_client, did_not_upload, error_log) + + frappe.connect() + return did_not_upload, list(set(error_log)) + +def upload_from_folder(path, dropbox_folder, dropbox_client, did_not_upload, error_log): + import dropbox.rest + + try: + response = dropbox_client.metadata(dropbox_folder) + except dropbox.rest.ErrorResponse, e: + # folder not found + if e.status==404: + response = {"contents": []} + for filename in os.listdir(path): filename = cstr(filename) @@ -162,16 +179,14 @@ def backup_to_dropbox(): if os.path.basename(filepath) == os.path.basename(file_metadata["path"]) and os.stat(filepath).st_size == int(file_metadata["bytes"]): found = True break + if not found: try: - upload_file_to_dropbox(filepath, "/files", dropbox_client) + upload_file_to_dropbox(filepath, dropbox_folder, dropbox_client) except Exception: did_not_upload.append(filename) error_log.append(frappe.get_traceback()) - frappe.connect() - return did_not_upload, list(set(error_log)) - def get_dropbox_session(): try: from dropbox import session diff --git a/frappe/public/css/docs.css b/frappe/public/css/docs.css index baae9ce26b..79bb3fe658 100644 --- a/frappe/public/css/docs.css +++ b/frappe/public/css/docs.css @@ -516,7 +516,7 @@ p { .fake-browser-frame { position: relative; margin: 24px auto 0px; - box-shadow: 0px -6px 100px 1px rgba(0, 0, 0, 0.1); + box-shadow: 0px -6px 100px 1px rgba(0, 0, 0, 0.1), 0px -6px 50px 1px rgba(0, 0, 0, 0.4); } .fake-browser-frame::before { content: ""; diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 25f5bfc812..ab43d023a2 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -810,13 +810,14 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ onerror: function() { me.dialog.hide(); }, + is_private: this.df.is_private } if(this.frm) { this.upload_options.args = { from_form: 1, doctype: this.frm.doctype, - docname: this.frm.docname, + docname: this.frm.docname } } else { this.upload_options.on_attach = function(fileobj, dataurl) { diff --git a/frappe/public/js/frappe/form/footer/attachments.js b/frappe/public/js/frappe/form/footer/attachments.js index 5c68d8b627..fa1e23634b 100644 --- a/frappe/public/js/frappe/form/footer/attachments.js +++ b/frappe/public/js/frappe/form/footer/attachments.js @@ -64,10 +64,12 @@ frappe.ui.form.Attachments = Class.extend({ var me = this; var $attach = $(repl('
  • \ ×\ + %(lock_icon)s\ \ %(file_name)s\
  • ', { + lock_icon: attachment.is_private ? ' ': "", file_name: file_name, file_url: frappe.urllib.get_full_url(file_url) })) @@ -149,7 +151,7 @@ frappe.ui.form.Attachments = Class.extend({ // remove upload dialog this.dialog.$wrapper.remove(); } - + // make upload dialog this.dialog = frappe.ui.get_upload_dialog({ "args": me.get_args(), diff --git a/frappe/public/js/frappe/form/footer/timeline_item.html b/frappe/public/js/frappe/form/footer/timeline_item.html index b26b23bb59..e8bcfa6cae 100644 --- a/frappe/public/js/frappe/form/footer/timeline_item.html +++ b/frappe/public/js/frappe/form/footer/timeline_item.html @@ -78,9 +78,12 @@
    {% $.each(data.attachments, function(i, a) { %} {% }); %} diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 9529e12225..b430fd8b2d 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -30,6 +30,10 @@ frappe.ui.Dialog = frappe.ui.FieldGroup.extend({ this.set_primary_action(this.primary_action_label || __("Submit"), this.primary_action); } + if (this.secondary_action_label) { + this.get_close_btn().html(this.secondary_action_label); + } + var me = this; this.$wrapper .on("hide.bs.modal", function() { diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index 3b0e4b2c63..744629c548 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -28,7 +28,8 @@ frappe.confirm = function(message, ifyes, ifno) { primary_action: function() { ifyes(); d.hide(); - } + }, + secondary_action_label: __("No") }); d.show(); diff --git a/frappe/public/js/frappe/ui/upload.html b/frappe/public/js/frappe/ui/upload.html index 98bf25d8c4..134818e51b 100644 --- a/frappe/public/js/frappe/ui/upload.html +++ b/frappe/public/js/frappe/ui/upload.html @@ -1,21 +1,27 @@
    -
    - - -
    - -