From 8a5addaae759837126530a34e9e45074b7396543 Mon Sep 17 00:00:00 2001
From: Anand Doshi
Date: Tue, 8 Dec 2015 18:26:44 +0530
Subject: [PATCH] [feature] Private files. Fixes #927
- Option during upload + all new incoming email files will be private
- Paired with @rmehta
---
.travis.yml | 1 -
frappe/app.py | 3 +
frappe/change_log/current/private_files.md | 4 +
frappe/commands.py | 8 +-
frappe/core/doctype/file/file.js | 6 +-
frappe/core/doctype/file/file.json | 48 ++++-
frappe/core/doctype/file/file.py | 12 ++
frappe/core/doctype/file/file_list.js | 21 ++-
frappe/desk/form/load.py | 8 +-
frappe/desk/page/backups/backups.py | 1 +
.../api/utils/frappe.utils.backups.html | 6 +-
.../api/utils/frappe.utils.file_manager.html | 10 +-
.../api/utils/frappe.utils.response.html | 16 ++
frappe/docs/current/index.html | 2 +-
frappe/docs/current/models/core/file.html | 69 +++++--
.../models/integrations/dropbox_backup.html | 16 ++
frappe/email/receive.py | 2 +-
.../doctype/dropbox_backup/dropbox_backup.py | 35 +++-
frappe/public/css/docs.css | 2 +-
frappe/public/js/frappe/form/control.js | 3 +-
.../js/frappe/form/footer/attachments.js | 4 +-
.../js/frappe/form/footer/timeline_item.html | 7 +-
frappe/public/js/frappe/ui/dialog.js | 4 +
frappe/public/js/frappe/ui/messages.js | 3 +-
frappe/public/js/frappe/ui/upload.html | 36 ++--
frappe/public/js/frappe/upload.js | 178 +++++++++++-------
frappe/public/js/legacy/form.js | 3 +-
frappe/public/less/docs.less | 2 +-
frappe/public/less/sidebar.less | 6 +
frappe/utils/__init__.py | 4 +-
frappe/utils/backups.py | 57 ++++--
frappe/utils/file_manager.py | 84 ++++++---
frappe/utils/pdf.py | 7 +-
frappe/utils/response.py | 19 +-
requirements.txt | 2 +-
setup.py | 26 +--
36 files changed, 496 insertions(+), 219 deletions(-)
create mode 100644 frappe/change_log/current/private_files.md
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 dbfile name, 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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -340,7 +340,7 @@
frappe.utils.file_manager.
save_uploaded
-
(dt, dn, folder)
+
(dt, dn, folder, is_private)
@@ -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)
+
+
+
+
+
+
+
+
+
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)
+
+
+
+
+
+
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 @@
-
-
- {%= __("Browse") %}
-
-
-