[feature] Private files. Fixes #927
- Option during upload + all new incoming email files will be private - Paired with @rmehta
This commit is contained in:
parent
80c6457f0a
commit
8a5addaae7
36 changed files with 496 additions and 219 deletions
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
4
frappe/change_log/current/private_files.md
Normal file
4
frappe/change_log/current/private_files.md
Normal file
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = '<i class="icon-folder-close-alt icon-fixed-width"></i> ' + data.file_name;
|
||||
icon += '<i class="icon-folder-close-alt icon-fixed-width"></i> ';
|
||||
} else if(frappe.utils.is_image_file(data.file_name)) {
|
||||
data._title = '<i class="icon-picture icon-fixed-width"></i> ' + data.file_name;
|
||||
icon += '<i class="icon-picture icon-fixed-width"></i> ';
|
||||
} else {
|
||||
data._title = '<i class="icon-file-alt icon-fixed-width"></i> \
|
||||
' + (data.file_name ? data.file_name : data.file_url);
|
||||
icon += '<i class="icon-file-alt icon-fixed-width"></i> '
|
||||
}
|
||||
|
||||
data._title = icon + (data.file_name ? data.file_name : data.file_url)
|
||||
|
||||
if (data.is_private) {
|
||||
data._title += ' <i class="icon-lock icon-fixed-width text-warning"></i>'
|
||||
}
|
||||
},
|
||||
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){
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ If specifying db<em>file</em>name, also append ".sql.gz"</p>
|
|||
<a name="__init__" href="#__init__" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
<b>__init__</b>
|
||||
<i class="text-muted">(self, db_name, user, password, backup_path_db=None, backup_path_files=None, db_host=localhost)</i>
|
||||
<i class="text-muted">(self, db_name, user, password, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, db_host=localhost)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p><span class="text-muted">No docs</span></p>
|
||||
</div>
|
||||
|
|
@ -223,7 +223,7 @@ False: file is new</p>
|
|||
<a name="frappe.utils.backups.new_backup" href="#frappe.utils.backups.new_backup" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.utils.backups.<b>new_backup</b>
|
||||
<i class="text-muted">(older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, force=False)</i>
|
||||
<i class="text-muted">(older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, force=False)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p><span class="text-muted">No docs</span></p>
|
||||
</div>
|
||||
|
|
@ -239,7 +239,7 @@ False: file is new</p>
|
|||
<a name="frappe.utils.backups.scheduled_backup" href="#frappe.utils.backups.scheduled_backup" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.utils.backups.<b>scheduled_backup</b>
|
||||
<i class="text-muted">(older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, force=False)</i>
|
||||
<i class="text-muted">(older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, force=False)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p>this function is called from scheduler
|
||||
deletes backups older than 7 days
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@
|
|||
<a name="frappe.utils.file_manager.get_file_data_from_hash" href="#frappe.utils.file_manager.get_file_data_from_hash" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.utils.file_manager.<b>get_file_data_from_hash</b>
|
||||
<i class="text-muted">(content_hash)</i>
|
||||
<i class="text-muted">(content_hash, is_private=0)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p><span class="text-muted">No docs</span></p>
|
||||
</div>
|
||||
|
|
@ -308,7 +308,7 @@
|
|||
<a name="frappe.utils.file_manager.save_file" href="#frappe.utils.file_manager.save_file" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.utils.file_manager.<b>save_file</b>
|
||||
<i class="text-muted">(fname, content, dt, dn, folder=None, decode=False)</i>
|
||||
<i class="text-muted">(fname, content, dt, dn, folder=None, decode=False, is_private=0)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p><span class="text-muted">No docs</span></p>
|
||||
</div>
|
||||
|
|
@ -324,7 +324,7 @@
|
|||
<a name="frappe.utils.file_manager.save_file_on_filesystem" href="#frappe.utils.file_manager.save_file_on_filesystem" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.utils.file_manager.<b>save_file_on_filesystem</b>
|
||||
<i class="text-muted">(fname, content, content_type=None)</i>
|
||||
<i class="text-muted">(fname, content, content_type=None, is_private=0)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p><span class="text-muted">No docs</span></p>
|
||||
</div>
|
||||
|
|
@ -340,7 +340,7 @@
|
|||
<a name="frappe.utils.file_manager.save_uploaded" href="#frappe.utils.file_manager.save_uploaded" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.utils.file_manager.<b>save_uploaded</b>
|
||||
<i class="text-muted">(dt, dn, folder)</i>
|
||||
<i class="text-muted">(dt, dn, folder, is_private)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p><span class="text-muted">No docs</span></p>
|
||||
</div>
|
||||
|
|
@ -388,7 +388,7 @@
|
|||
<a name="frappe.utils.file_manager.write_file" href="#frappe.utils.file_manager.write_file" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.utils.file_manager.<b>write_file</b>
|
||||
<i class="text-muted">(content, file_path, fname)</i>
|
||||
<i class="text-muted">(content, fname, is_private=0)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p>write file to disk with a random name (to compare)</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -113,6 +113,22 @@
|
|||
|
||||
|
||||
|
||||
<p class="docs-attr-name">
|
||||
<a name="frappe.utils.response.download_private_file" href="#frappe.utils.response.download_private_file" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.utils.response.<b>download_private_file</b>
|
||||
<i class="text-muted">(path)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p>Checks permissions and sends back private file</p>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<p class="docs-attr-name">
|
||||
<a name="frappe.utils.response.handle_session_stopped" href="#frappe.utils.response.handle_session_stopped" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
Version
|
||||
</td>
|
||||
<td>
|
||||
<code>6.12.3</code>
|
||||
<code>6.12.4</code>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -50,8 +50,20 @@
|
|||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr class="info">
|
||||
<tr >
|
||||
<td>2</td>
|
||||
<td ><code>is_private</code></td>
|
||||
<td >
|
||||
Check</td>
|
||||
<td >
|
||||
Is Private
|
||||
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr class="info">
|
||||
<td>3</td>
|
||||
<td ><code>preview</code></td>
|
||||
<td >
|
||||
Section Break</td>
|
||||
|
|
@ -63,7 +75,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td ><code>preview_html</code></td>
|
||||
<td >
|
||||
HTML</td>
|
||||
|
|
@ -75,7 +87,7 @@
|
|||
</tr>
|
||||
|
||||
<tr class="info">
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td ><code>section_break_5</code></td>
|
||||
<td >
|
||||
Section Break</td>
|
||||
|
|
@ -87,7 +99,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>5</td>
|
||||
<td>6</td>
|
||||
<td ><code>is_home_folder</code></td>
|
||||
<td >
|
||||
Check</td>
|
||||
|
|
@ -99,7 +111,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>6</td>
|
||||
<td>7</td>
|
||||
<td ><code>is_attachments_folder</code></td>
|
||||
<td >
|
||||
Check</td>
|
||||
|
|
@ -111,7 +123,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>7</td>
|
||||
<td>8</td>
|
||||
<td ><code>file_size</code></td>
|
||||
<td >
|
||||
Int</td>
|
||||
|
|
@ -123,7 +135,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>8</td>
|
||||
<td>9</td>
|
||||
<td ><code>column_break_5</code></td>
|
||||
<td class="info">
|
||||
Column Break</td>
|
||||
|
|
@ -135,7 +147,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>9</td>
|
||||
<td>10</td>
|
||||
<td ><code>file_url</code></td>
|
||||
<td >
|
||||
Small Text</td>
|
||||
|
|
@ -147,7 +159,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>10</td>
|
||||
<td>11</td>
|
||||
<td ><code>thumbnail_url</code></td>
|
||||
<td >
|
||||
Small Text</td>
|
||||
|
|
@ -159,7 +171,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>11</td>
|
||||
<td>12</td>
|
||||
<td ><code>folder</code></td>
|
||||
<td >
|
||||
Link</td>
|
||||
|
|
@ -180,7 +192,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>12</td>
|
||||
<td>13</td>
|
||||
<td ><code>is_folder</code></td>
|
||||
<td >
|
||||
Check</td>
|
||||
|
|
@ -192,7 +204,7 @@
|
|||
</tr>
|
||||
|
||||
<tr class="info">
|
||||
<td>13</td>
|
||||
<td>14</td>
|
||||
<td ><code>section_break_8</code></td>
|
||||
<td >
|
||||
Section Break</td>
|
||||
|
|
@ -204,7 +216,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>14</td>
|
||||
<td>15</td>
|
||||
<td ><code>attached_to_doctype</code></td>
|
||||
<td >
|
||||
Link</td>
|
||||
|
|
@ -225,7 +237,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>15</td>
|
||||
<td>16</td>
|
||||
<td ><code>column_break_10</code></td>
|
||||
<td class="info">
|
||||
Column Break</td>
|
||||
|
|
@ -237,7 +249,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>16</td>
|
||||
<td>17</td>
|
||||
<td ><code>attached_to_name</code></td>
|
||||
<td >
|
||||
Data</td>
|
||||
|
|
@ -249,7 +261,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>17</td>
|
||||
<td>18</td>
|
||||
<td ><code>content_hash</code></td>
|
||||
<td >
|
||||
Data</td>
|
||||
|
|
@ -261,7 +273,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>18</td>
|
||||
<td>19</td>
|
||||
<td ><code>lft</code></td>
|
||||
<td >
|
||||
Int</td>
|
||||
|
|
@ -273,7 +285,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>19</td>
|
||||
<td>20</td>
|
||||
<td ><code>rgt</code></td>
|
||||
<td >
|
||||
Int</td>
|
||||
|
|
@ -285,7 +297,7 @@
|
|||
</tr>
|
||||
|
||||
<tr >
|
||||
<td>20</td>
|
||||
<td>21</td>
|
||||
<td ><code>old_parent</code></td>
|
||||
<td >
|
||||
Data</td>
|
||||
|
|
@ -607,7 +619,8 @@
|
|||
<b>validate_file</b>
|
||||
<i class="text-muted">(self)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p><span class="text-muted">No docs</span></p>
|
||||
<div class="docs-attr-desc"><p>Validates existence of public file
|
||||
TODO: validate for private file</p>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
|
|
@ -649,6 +662,22 @@
|
|||
|
||||
|
||||
|
||||
|
||||
<p class="docs-attr-name">
|
||||
<a name="frappe.core.doctype.file.file.check_file_permission" href="#frappe.core.doctype.file.file.check_file_permission" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.core.doctype.file.file.<b>check_file_permission</b>
|
||||
<i class="text-muted">(file_url)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p><span class="text-muted">No docs</span></p>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<p><span class="label label-info">Public API</span>
|
||||
<br><code>/api/method/frappe.core.doctype.file.file.create_new_folder</code>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -316,6 +316,22 @@ Weekly</pre>
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<p class="docs-attr-name">
|
||||
<a name="frappe.integrations.doctype.dropbox_backup.dropbox_backup.upload_from_folder" href="#frappe.integrations.doctype.dropbox_backup.dropbox_backup.upload_from_folder" class="text-muted small">
|
||||
<i class="icon-link small" style="color: #ccc;"></i></a>
|
||||
frappe.integrations.doctype.dropbox_backup.dropbox_backup.<b>upload_from_folder</b>
|
||||
<i class="text-muted">(path, dropbox_folder, dropbox_client, did_not_upload, error_log)</i>
|
||||
</p>
|
||||
<div class="docs-attr-desc"><p><span class="text-muted">No docs</span></p>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: "";
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -64,10 +64,12 @@ frappe.ui.form.Attachments = Class.extend({
|
|||
var me = this;
|
||||
var $attach = $(repl('<li class="attachment-row">\
|
||||
<a class="close" data-owner="%(owner)s">×</a>\
|
||||
%(lock_icon)s\
|
||||
<a href="%(file_url)s" target="_blank" title="%(file_name)s" \
|
||||
class="text-ellipsis" style="max-width: calc(100% - 43px);">\
|
||||
<span>%(file_name)s</span></a>\
|
||||
</li>', {
|
||||
lock_icon: attachment.is_private ? '<i class="icon icon-lock icon-fixed-width text-warning"></i> ': "",
|
||||
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(),
|
||||
|
|
|
|||
|
|
@ -78,9 +78,12 @@
|
|||
<div style="margin: 10px 0px">
|
||||
{% $.each(data.attachments, function(i, a) { %}
|
||||
<div class="text-ellipsis">
|
||||
<a href="{%= a %}" class="text-muted small" target="_blank">
|
||||
<a href="{%= a.file_url.replace(/#/g, \'%23\') %}" class="text-muted small" target="_blank">
|
||||
<i class="icon-paperclip"></i>
|
||||
{%= a.split("/").slice(-1)[0] %}
|
||||
{%= a.file_url.split("/").slice(-1)[0] %}
|
||||
{% if (a.is_private) { %}
|
||||
<i class="icon icon-lock text-warning"></i>
|
||||
{% } %}
|
||||
</a>
|
||||
</div>
|
||||
{% }); %}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ frappe.confirm = function(message, ifyes, ifno) {
|
|||
primary_action: function() {
|
||||
ifyes();
|
||||
d.hide();
|
||||
}
|
||||
},
|
||||
secondary_action_label: __("No")
|
||||
});
|
||||
d.show();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
<div class="file-upload">
|
||||
<div class="input-upload">
|
||||
<input class="input-upload-file hidden" type="file" name="filedata" />
|
||||
<button class="btn btn-default btn-sm btn-browse">{%= __("Browse") %}</button>
|
||||
</div>
|
||||
<div class="uploaded-filename hidden" style="width: calc(100% - 67px);"></div>
|
||||
<div class="web-link-wrapper" style="width: calc(100% - 67px);">
|
||||
<span class="text-muted file-upload-or">{%= __("or") %}</span>
|
||||
<div class="input-link" style="width: calc(100% - 30px);">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<span class="hidden-xs">{%= __("Web Link") %}</span>
|
||||
<i class="icon-link visible-xs"></i>
|
||||
</div>
|
||||
<input class="form-control" type="text" name="file_url"
|
||||
placeholder="{%= (opts.sample_url || "e.g. http://example.com/somefile.png") %}"/>
|
||||
<div class="input-upload">
|
||||
<input class="input-upload-file hidden" type="file" name="filedata" />
|
||||
<button class="btn btn-primary btn-sm btn-browse">{%= __("Browse") %}</button>
|
||||
</div>
|
||||
<div class="uploaded-filename hidden" style="width: calc(100% - 67px);"></div>
|
||||
<div class="web-link-wrapper" style="width: calc(100% - 67px);">
|
||||
<span class="text-muted file-upload-or">{%= __("or") %}</span>
|
||||
<div class="input-link" style="width: calc(100% - 30px);">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<span class="hidden-xs">{%= __("Web Link") %}</span>
|
||||
<i class="icon-link visible-xs"></i>
|
||||
</div>
|
||||
<input class="form-control" type="text" name="file_url"
|
||||
placeholder="{%= (opts.sample_url || "e.g. http://example.com/somefile.png") %}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="private-file hidden">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" checked> {{ __("Private") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ frappe.upload = {
|
|||
$file_input.on("change", function() {
|
||||
if (this.files.length > 0) {
|
||||
$upload.find(".web-link-wrapper").addClass("hidden");
|
||||
$upload.find(".btn-browse").removeClass("btn-primary").addClass("btn-default");
|
||||
|
||||
var $uploaded_file_display = $(repl('<div class="btn-group" role="group">\
|
||||
<button type="button" class="btn btn-default btn-sm \
|
||||
|
|
@ -38,9 +39,16 @@ frappe.upload = {
|
|||
opts.on_select();
|
||||
}
|
||||
|
||||
if ( !("is_private" in opts) ) {
|
||||
// show Private checkbox
|
||||
$upload.find(".private-file").removeClass("hidden");
|
||||
}
|
||||
|
||||
} else {
|
||||
$upload.find(".uploaded-filename").addClass("hidden")
|
||||
$upload.find(".web-link-wrapper").removeClass("hidden");
|
||||
$upload.find(".private-file").addClass("hidden");
|
||||
$upload.find(".btn-browse").removeClass("btn-default").addClass("btn-primary");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -61,6 +69,7 @@ frappe.upload = {
|
|||
}
|
||||
|
||||
opts.args.file_url = $upload.find('[name="file_url"]').val();
|
||||
opts.args.is_private = $upload.find('.private-file input').prop('checked') ? 1 : 0;
|
||||
|
||||
var fileobj = $upload.find(":file").get(0).files[0];
|
||||
frappe.upload.upload_file(fileobj, opts.args, opts);
|
||||
|
|
@ -76,82 +85,107 @@ frappe.upload = {
|
|||
return;
|
||||
}
|
||||
|
||||
var dataurl = null;
|
||||
var _upload_file = function() {
|
||||
if (args.file_size) {
|
||||
frappe.upload.validate_max_file_size(args.file_size);
|
||||
}
|
||||
|
||||
if(opts.on_attach) {
|
||||
opts.on_attach(args, dataurl)
|
||||
} else {
|
||||
var msgbox = msgprint(__("Uploading..."));
|
||||
if(opts.start) {
|
||||
opts.start();
|
||||
}
|
||||
ajax_args = {
|
||||
"method": "uploadfile",
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
if(!r._server_messages)
|
||||
msgbox.hide();
|
||||
if(r.exc) {
|
||||
// if no onerror, assume callback will handle errors
|
||||
opts.onerror ? opts.onerror(r) : opts.callback(null, null, r);
|
||||
return;
|
||||
}
|
||||
var attachment = r.message;
|
||||
opts.callback(attachment, r);
|
||||
$(document).trigger("upload_complete", attachment);
|
||||
},
|
||||
error: function(r) {
|
||||
// if no onerror, assume callback will handle errors
|
||||
opts.onerror ? opts.onerror(r) : opts.callback(null, null, r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// copy handlers etc from opts
|
||||
$.each(['queued', 'running', "progress", "always", "btn"], function(i, key) {
|
||||
if(opts[key]) ajax_args[key] = opts[key];
|
||||
});
|
||||
return frappe.call(ajax_args);
|
||||
}
|
||||
}
|
||||
|
||||
if(args.file_url) {
|
||||
_upload_file();
|
||||
frappe.upload._upload_file(fileobj, args, opts);
|
||||
} else {
|
||||
var freader = new FileReader();
|
||||
|
||||
freader.onload = function() {
|
||||
args.filename = fileobj.name;
|
||||
if(opts.options && opts.options.toLowerCase()=="image") {
|
||||
if(!frappe.utils.is_image_file(args.filename)) {
|
||||
msgprint(__("Only image extensions (.gif, .jpg, .jpeg, .tiff, .png, .svg) allowed"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if((opts.max_width || opts.max_height) && frappe.utils.is_image_file(args.filename)) {
|
||||
frappe.utils.resize_image(freader, function(_dataurl) {
|
||||
dataurl = _dataurl;
|
||||
args.filedata = _dataurl.split(",")[1];
|
||||
args.file_size = Math.round(args.filedata.length * 3 / 4);
|
||||
console.log("resized!")
|
||||
_upload_file();
|
||||
})
|
||||
} else {
|
||||
dataurl = freader.result;
|
||||
args.filedata = freader.result.split(",")[1];
|
||||
args.file_size = fileobj.size;
|
||||
_upload_file();
|
||||
}
|
||||
};
|
||||
|
||||
freader.readAsDataURL(fileobj);
|
||||
frappe.upload.read_file(fileobj, args, opts);
|
||||
}
|
||||
},
|
||||
|
||||
_upload_file: function(fileobj, args, opts, dataurl) {
|
||||
if (args.file_size) {
|
||||
frappe.upload.validate_max_file_size(args.file_size);
|
||||
}
|
||||
|
||||
if(opts.on_attach) {
|
||||
opts.on_attach(args, dataurl)
|
||||
} else {
|
||||
if (opts.confirm_is_private) {
|
||||
frappe.prompt({
|
||||
label: __("Private"),
|
||||
fieldname: "is_private",
|
||||
fieldtype: "Check",
|
||||
"default": 1
|
||||
}, function(values) {
|
||||
args["is_private"] = values.is_private;
|
||||
frappe.upload.upload_to_server(fileobj, args, opts, dataurl);
|
||||
}, __("Private or Public?"));
|
||||
} else {
|
||||
if ("is_private" in opts) {
|
||||
args["is_private"] = opts.is_private;
|
||||
}
|
||||
|
||||
frappe.upload.upload_to_server(fileobj, args, opts, dataurl);
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
read_file: function(fileobj, args, opts) {
|
||||
var freader = new FileReader();
|
||||
|
||||
freader.onload = function() {
|
||||
args.filename = fileobj.name;
|
||||
if(opts.options && opts.options.toLowerCase()=="image") {
|
||||
if(!frappe.utils.is_image_file(args.filename)) {
|
||||
msgprint(__("Only image extensions (.gif, .jpg, .jpeg, .tiff, .png, .svg) allowed"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if((opts.max_width || opts.max_height) && frappe.utils.is_image_file(args.filename)) {
|
||||
frappe.utils.resize_image(freader, function(_dataurl) {
|
||||
dataurl = _dataurl;
|
||||
args.filedata = _dataurl.split(",")[1];
|
||||
args.file_size = Math.round(args.filedata.length * 3 / 4);
|
||||
console.log("resized!")
|
||||
frappe.upload._upload_file(fileobj, args, opts, dataurl);
|
||||
})
|
||||
} else {
|
||||
dataurl = freader.result;
|
||||
args.filedata = freader.result.split(",")[1];
|
||||
args.file_size = fileobj.size;
|
||||
frappe.upload._upload_file(fileobj, args, opts, dataurl);
|
||||
}
|
||||
};
|
||||
|
||||
freader.readAsDataURL(fileobj);
|
||||
},
|
||||
|
||||
upload_to_server: function(fileobj, args, opts, dataurl) {
|
||||
var msgbox = msgprint(__("Uploading..."));
|
||||
if(opts.start) {
|
||||
opts.start();
|
||||
}
|
||||
ajax_args = {
|
||||
"method": "uploadfile",
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
if(!r._server_messages)
|
||||
msgbox.hide();
|
||||
if(r.exc) {
|
||||
// if no onerror, assume callback will handle errors
|
||||
opts.onerror ? opts.onerror(r) : opts.callback(null, null, r);
|
||||
return;
|
||||
}
|
||||
var attachment = r.message;
|
||||
opts.callback(attachment, r);
|
||||
$(document).trigger("upload_complete", attachment);
|
||||
},
|
||||
error: function(r) {
|
||||
// if no onerror, assume callback will handle errors
|
||||
opts.onerror ? opts.onerror(r) : opts.callback(null, null, r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// copy handlers etc from opts
|
||||
$.each(['queued', 'running', "progress", "always", "btn"], function(i, key) {
|
||||
if(opts[key]) ajax_args[key] = opts[key];
|
||||
});
|
||||
return frappe.call(ajax_args);
|
||||
},
|
||||
|
||||
get_string: function(dataURI) {
|
||||
// remove filename
|
||||
var parts = dataURI.split(',');
|
||||
|
|
|
|||
|
|
@ -143,7 +143,8 @@ _f.Frm.prototype.setup_drag_drop = function() {
|
|||
frappe.upload.upload_file(dataTransfer.files[0], me.attachments.get_args(), {
|
||||
callback: function(attachment, r) {
|
||||
me.attachments.attachment_uploaded(attachment, r);
|
||||
}
|
||||
},
|
||||
confirm_is_private: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,7 +286,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 {
|
||||
|
|
|
|||
|
|
@ -111,6 +111,12 @@ body[data-route^="Module"] .main-menu {
|
|||
right: 5px;
|
||||
}
|
||||
|
||||
// .attachment-row .icon-lock {
|
||||
// color: @text-warning;
|
||||
// display: inline-block;
|
||||
// margin-top: 1px;
|
||||
// }
|
||||
|
||||
.attachment-row a.close {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -314,8 +314,8 @@ def get_site_base_path(sites_dir=None, hostname=None):
|
|||
def get_site_path(*path):
|
||||
return get_path(base=get_site_base_path(), *path)
|
||||
|
||||
def get_files_path(*path):
|
||||
return get_site_path("public", "files", *path)
|
||||
def get_files_path(*path, **kwargs):
|
||||
return get_site_path("private" if kwargs.get("is_private") else "public", "files", *path)
|
||||
|
||||
def get_bench_path():
|
||||
return os.path.realpath(os.path.join(os.path.dirname(frappe.__file__), '..', '..', '..'))
|
||||
|
|
|
|||
|
|
@ -22,13 +22,15 @@ class BackupGenerator:
|
|||
To initialize, specify (db_name, user, password, db_file_name=None, db_host="localhost")
|
||||
If specifying db_file_name, also append ".sql.gz"
|
||||
"""
|
||||
def __init__(self, db_name, user, password, backup_path_db=None, backup_path_files=None, db_host="localhost"):
|
||||
def __init__(self, db_name, user, password, backup_path_db=None, backup_path_files=None,
|
||||
backup_path_private_files=None, db_host="localhost"):
|
||||
self.db_host = db_host
|
||||
self.db_name = db_name
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.backup_path_files = backup_path_files
|
||||
self.backup_path_db = backup_path_db
|
||||
self.backup_path_private_files = backup_path_private_files
|
||||
|
||||
def get_backup(self, older_than=24, ignore_files=False, force=False):
|
||||
"""
|
||||
|
|
@ -38,20 +40,22 @@ class BackupGenerator:
|
|||
#Check if file exists and is less than a day old
|
||||
#If not Take Dump
|
||||
if not force:
|
||||
last_db, last_file = self.get_recent_backup(older_than)
|
||||
last_db, last_file, last_private_file = self.get_recent_backup(older_than)
|
||||
else:
|
||||
last_db, last_file = False, False
|
||||
|
||||
if not (self.backup_path_files and self.backup_path_db):
|
||||
last_db, last_file, last_private_file = False, False, False
|
||||
|
||||
if not (self.backup_path_files and self.backup_path_db and self.backup_path_private_files):
|
||||
self.set_backup_file_name()
|
||||
if not (last_db and last_file):
|
||||
|
||||
if not (last_db and last_file and last_private_file):
|
||||
self.take_dump()
|
||||
if not ignore_files:
|
||||
self.zip_files()
|
||||
|
||||
else:
|
||||
self.backup_path_files = last_file
|
||||
self.backup_path_db = last_db
|
||||
|
||||
self.backup_path_private_files = last_private_file
|
||||
|
||||
def set_backup_file_name(self):
|
||||
import random
|
||||
|
|
@ -60,32 +64,45 @@ class BackupGenerator:
|
|||
|
||||
#Generate a random name using today's date and a 8 digit random number
|
||||
for_db = todays_date + "_" + random_number + "_database.sql.gz"
|
||||
for_files = todays_date + "_" + random_number + "_files.tar"
|
||||
for_public_files = todays_date + "_" + random_number + "_files.tar"
|
||||
for_private_files = todays_date + "_" + random_number + "_private_files.tar"
|
||||
backup_path = get_backup_path()
|
||||
|
||||
if not self.backup_path_db:
|
||||
self.backup_path_db = os.path.join(backup_path, for_db)
|
||||
if not self.backup_path_files:
|
||||
self.backup_path_files = os.path.join(backup_path, for_files)
|
||||
self.backup_path_files = os.path.join(backup_path, for_public_files)
|
||||
if not self.backup_path_private_files:
|
||||
self.backup_path_private_files = os.path.join(backup_path, for_private_files)
|
||||
|
||||
def get_recent_backup(self, older_than):
|
||||
file_list = os.listdir(get_backup_path())
|
||||
backup_path_files = None
|
||||
backup_path_db = None
|
||||
backup_path_private_files = None
|
||||
|
||||
for this_file in file_list:
|
||||
this_file = cstr(this_file)
|
||||
this_file_path = os.path.join(get_backup_path(), this_file)
|
||||
if not is_file_old(this_file_path, older_than):
|
||||
if "_files" in this_file_path:
|
||||
if "_private_files" in this_file_path:
|
||||
backup_path_private_files = this_file_path
|
||||
elif "_files" in this_file_path:
|
||||
backup_path_files = this_file_path
|
||||
if "_database" in this_file_path:
|
||||
elif "_database" in this_file_path:
|
||||
backup_path_db = this_file_path
|
||||
return (backup_path_db, backup_path_files)
|
||||
|
||||
return (backup_path_db, backup_path_files, backup_path_private_files)
|
||||
|
||||
def zip_files(self):
|
||||
files_path = frappe.get_site_path("public", "files")
|
||||
cmd_string = """tar -cf %s %s""" % (self.backup_path_files, files_path)
|
||||
err, out = frappe.utils.execute_in_shell(cmd_string)
|
||||
print 'Backed up files', os.path.abspath(self.backup_path_files)
|
||||
for folder in ("public", "private"):
|
||||
files_path = frappe.get_site_path(folder, "files")
|
||||
backup_path = self.backup_path_files if folder=="public" else self.backup_path_private_files
|
||||
|
||||
cmd_string = """tar -cf %s %s""" % (backup_path, files_path)
|
||||
err, out = frappe.utils.execute_in_shell(cmd_string)
|
||||
|
||||
print 'Backed up files', os.path.abspath(backup_path)
|
||||
|
||||
def take_dump(self):
|
||||
import frappe.utils
|
||||
|
|
@ -140,18 +157,20 @@ def get_backup():
|
|||
recipient_list = odb.send_email()
|
||||
frappe.msgprint(_("Download link for your backup will be emailed on the following email address: {0}").format(', '.join(recipient_list)))
|
||||
|
||||
def scheduled_backup(older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, force=False):
|
||||
def scheduled_backup(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
|
||||
takes backup"""
|
||||
odb = new_backup(older_than, ignore_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, force=force)
|
||||
return odb
|
||||
|
||||
def new_backup(older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, force=False):
|
||||
def new_backup(older_than=6, ignore_files=False, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, force=False):
|
||||
delete_temp_backups(older_than = frappe.conf.keep_backups_for_hours or 48)
|
||||
odb = BackupGenerator(frappe.conf.db_name, frappe.conf.db_name,\
|
||||
frappe.conf.db_password,
|
||||
backup_path_db=backup_path_db, backup_path_files=backup_path_files, db_host = frappe.db.host)
|
||||
backup_path_db=backup_path_db, backup_path_files=backup_path_files,
|
||||
backup_path_private_files=backup_path_private_files,
|
||||
db_host = frappe.db.host)
|
||||
odb.get_backup(older_than, ignore_files, force=force)
|
||||
return odb
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import frappe
|
|||
import os, base64, re
|
||||
import hashlib
|
||||
import mimetypes
|
||||
from frappe.utils import get_site_path, get_hook_method, get_files_path, random_string, encode, cstr, call_hook_method
|
||||
from frappe.utils import get_site_path, get_hook_method, get_files_path, random_string, encode, cstr, call_hook_method, cint
|
||||
from frappe import _
|
||||
from frappe import conf
|
||||
from copy import copy
|
||||
|
|
@ -25,6 +25,7 @@ def upload():
|
|||
folder = frappe.form_dict.folder
|
||||
file_url = frappe.form_dict.file_url
|
||||
filename = frappe.form_dict.filename
|
||||
is_private = cint(frappe.form_dict.is_private)
|
||||
|
||||
if not filename and not file_url:
|
||||
frappe.msgprint(_("Please select a file or url"),
|
||||
|
|
@ -32,14 +33,18 @@ def upload():
|
|||
|
||||
# save
|
||||
if filename:
|
||||
filedata = save_uploaded(dt, dn, folder)
|
||||
filedata = save_uploaded(dt, dn, folder, is_private)
|
||||
elif file_url:
|
||||
filedata = save_url(file_url, dt, dn, folder)
|
||||
|
||||
comment = {}
|
||||
if dt and dn:
|
||||
comment = frappe.get_doc(dt, dn).add_comment("Attachment",
|
||||
_("Added {0}").format("<a href='{file_url}' target='_blank'>{file_name}</a>".format(**filedata.as_dict())))
|
||||
_("Added {0}").format("<a href='{file_url}' target='_blank'>{file_name}</a>{icon}".format(**{
|
||||
"icon": ' <i class="icon icon-lock text-warning"></i>' if filedata.is_private else "",
|
||||
"file_url": filedata.file_url.replace("#", "%23") if filedata.file_name else filedata.file_url,
|
||||
"file_name": filedata.file_name or filedata.file_url
|
||||
})))
|
||||
|
||||
return {
|
||||
"name": filedata.name,
|
||||
|
|
@ -48,10 +53,10 @@ def upload():
|
|||
"comment": comment.as_dict() if comment else {}
|
||||
}
|
||||
|
||||
def save_uploaded(dt, dn, folder):
|
||||
def save_uploaded(dt, dn, folder, is_private):
|
||||
fname, content = get_uploaded_content()
|
||||
if content:
|
||||
return save_file(fname, content, dt, dn, folder);
|
||||
return save_file(fname, content, dt, dn, folder, is_private=is_private);
|
||||
else:
|
||||
raise Exception
|
||||
|
||||
|
|
@ -133,7 +138,7 @@ def get_random_filename(extn=None, content_type=None):
|
|||
|
||||
return random_string(7) + (extn or "")
|
||||
|
||||
def save_file(fname, content, dt, dn, folder=None, decode=False):
|
||||
def save_file(fname, content, dt, dn, folder=None, decode=False, is_private=0):
|
||||
if decode:
|
||||
if isinstance(content, unicode):
|
||||
content = content.encode("utf-8")
|
||||
|
|
@ -146,12 +151,12 @@ def save_file(fname, content, dt, dn, folder=None, decode=False):
|
|||
content_hash = get_content_hash(content)
|
||||
content_type = mimetypes.guess_type(fname)[0]
|
||||
fname = get_file_name(fname, content_hash[-6:])
|
||||
file_data = get_file_data_from_hash(content_hash)
|
||||
file_data = get_file_data_from_hash(content_hash, is_private=is_private)
|
||||
if not file_data:
|
||||
call_hook_method("before_write_file", file_size=file_size)
|
||||
|
||||
method = get_hook_method('write_file', fallback=save_file_on_filesystem)
|
||||
file_data = method(fname, content, content_type=content_type)
|
||||
write_file_method = get_hook_method('write_file', fallback=save_file_on_filesystem)
|
||||
file_data = write_file_method(fname, content, content_type=content_type, is_private=is_private)
|
||||
file_data = copy(file_data)
|
||||
|
||||
file_data.update({
|
||||
|
|
@ -161,6 +166,7 @@ def save_file(fname, content, dt, dn, folder=None, decode=False):
|
|||
"folder": folder,
|
||||
"file_size": file_size,
|
||||
"content_hash": content_hash,
|
||||
"is_private": is_private
|
||||
})
|
||||
|
||||
f = frappe.get_doc(file_data)
|
||||
|
|
@ -172,19 +178,23 @@ def save_file(fname, content, dt, dn, folder=None, decode=False):
|
|||
|
||||
return f
|
||||
|
||||
def get_file_data_from_hash(content_hash):
|
||||
for name in frappe.db.sql_list("select name from `tabFile` where content_hash=%s", content_hash):
|
||||
def get_file_data_from_hash(content_hash, is_private=0):
|
||||
for name in frappe.db.sql_list("select name from `tabFile` where content_hash=%s and is_private=%s", (content_hash, is_private)):
|
||||
b = frappe.get_doc('File', name)
|
||||
return {k:b.get(k) for k in frappe.get_hooks()['write_file_keys']}
|
||||
return False
|
||||
|
||||
def save_file_on_filesystem(fname, content, content_type=None):
|
||||
public_path = os.path.join(frappe.local.site_path, "public")
|
||||
fpath = write_file(content, get_files_path(), fname)
|
||||
path = os.path.relpath(fpath, public_path)
|
||||
def save_file_on_filesystem(fname, content, content_type=None, is_private=0):
|
||||
fpath = write_file(content, fname, is_private)
|
||||
|
||||
if is_private:
|
||||
file_url = "/private/files/{0}".format(fname)
|
||||
else:
|
||||
file_url = "/files/{0}".format(fname)
|
||||
|
||||
return {
|
||||
'file_name': os.path.basename(path),
|
||||
'file_url': '/' + path
|
||||
'file_name': os.path.basename(fpath),
|
||||
'file_url': file_url
|
||||
}
|
||||
|
||||
def check_max_file_size(content):
|
||||
|
|
@ -198,14 +208,17 @@ def check_max_file_size(content):
|
|||
|
||||
return file_size
|
||||
|
||||
def write_file(content, file_path, fname):
|
||||
def write_file(content, fname, is_private=0):
|
||||
"""write file to disk with a random name (to compare)"""
|
||||
file_path = get_files_path(is_private=is_private)
|
||||
|
||||
# create directory (if not exists)
|
||||
frappe.create_folder(get_files_path())
|
||||
frappe.create_folder(file_path)
|
||||
# write the file
|
||||
with open(os.path.join(file_path.encode('utf-8'), fname.encode('utf-8')), 'w+') as f:
|
||||
f.write(content)
|
||||
return get_files_path(fname)
|
||||
|
||||
return get_files_path(fname, is_private=is_private)
|
||||
|
||||
def remove_all(dt, dn):
|
||||
"""remove all files in a transaction"""
|
||||
|
|
@ -261,13 +274,17 @@ def delete_file_from_filesystem(doc, only_thumbnail=False):
|
|||
|
||||
def delete_file(path):
|
||||
"""Delete file from `public folder`"""
|
||||
if path and path.startswith("/files/"):
|
||||
parts = os.path.split(path)
|
||||
path = frappe.utils.get_site_path("public", "files", parts[-1])
|
||||
|
||||
if path:
|
||||
if ".." in path.split("/"):
|
||||
frappe.msgprint(_("It is risky to delete this file: {0}. Please contact your System Manager.").format(path))
|
||||
|
||||
parts = os.path.split(path.strip("/"))
|
||||
if parts[0]=="files":
|
||||
path = frappe.utils.get_site_path("public", "files", parts[-1])
|
||||
|
||||
else:
|
||||
path = frappe.utils.get_site_path("private", "files", parts[-1])
|
||||
|
||||
path = encode(path)
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
|
@ -277,22 +294,31 @@ def get_file(fname):
|
|||
file_path = get_file_path(fname)
|
||||
|
||||
# read the file
|
||||
with open(encode(get_site_path("public", file_path)), 'r') as f:
|
||||
with open(encode(file_path), 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
return [file_path.rsplit("/", 1)[-1], content]
|
||||
|
||||
def get_file_path(file_name):
|
||||
"""Returns file path from given file name"""
|
||||
f = frappe.db.sql("""select file_name from `tabFile`
|
||||
f = frappe.db.sql("""select file_url from `tabFile`
|
||||
where name=%s or file_name=%s""", (file_name, file_name))
|
||||
if f:
|
||||
file_name = f[0][0]
|
||||
file_url = f[0][0]
|
||||
|
||||
file_path = file_name
|
||||
file_path = file_url
|
||||
|
||||
if not "/" in file_path:
|
||||
file_path = "files/" + file_path
|
||||
file_path = "/files/" + file_path
|
||||
|
||||
if file_path.startswith("/private/files/"):
|
||||
file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1)
|
||||
|
||||
elif file_path.startswith("/files/"):
|
||||
file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/"))
|
||||
|
||||
else:
|
||||
frappe.throw(_("There is some problem with the file url: {0}").format(file_url))
|
||||
|
||||
return file_path
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ def get_pdf(html, options=None):
|
|||
'margin-left': '15mm',
|
||||
'encoding': "UTF-8",
|
||||
'quiet': None,
|
||||
'no-outline': None
|
||||
'no-outline': None,
|
||||
})
|
||||
|
||||
if frappe.session and frappe.session.sid:
|
||||
options['cookie'] = [('sid', '{0}'.format(frappe.session.sid))]
|
||||
|
||||
if not options.get("page-size"):
|
||||
options['page-size'] = frappe.db.get_single_value("Print Settings", "pdf_page_size") or "A4"
|
||||
|
||||
|
|
@ -30,7 +33,7 @@ def get_pdf(html, options=None):
|
|||
fname = os.path.join("/tmp", frappe.generate_hash() + ".pdf")
|
||||
|
||||
try:
|
||||
pdfkit.from_string(html, fname, options=options or {})
|
||||
pdfkit.from_string(html, fname, options=options or {}, )
|
||||
|
||||
with open(fname, "rb") as fileobj:
|
||||
filedata = fileobj.read()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ from werkzeug.local import LocalProxy
|
|||
from werkzeug.wsgi import wrap_file
|
||||
from werkzeug.wrappers import Response
|
||||
from werkzeug.exceptions import NotFound, Forbidden
|
||||
from frappe.core.doctype.file.file import check_file_permission
|
||||
from frappe.website.render import render
|
||||
|
||||
def report_error(status_code):
|
||||
if (status_code!=404 or frappe.conf.logging) and not frappe.local.flags.disable_traceback:
|
||||
|
|
@ -97,7 +99,6 @@ def json_handler(obj):
|
|||
|
||||
def as_page():
|
||||
"""print web page"""
|
||||
from frappe.website.render import render
|
||||
return render(frappe.response['page_name'], http_status_code=frappe.response.get("http_status_code"))
|
||||
|
||||
def redirect():
|
||||
|
|
@ -111,6 +112,17 @@ def download_backup(path):
|
|||
|
||||
return send_private_file(path)
|
||||
|
||||
def download_private_file(path):
|
||||
"""Checks permissions and sends back private file"""
|
||||
try:
|
||||
check_file_permission(path)
|
||||
|
||||
except frappe.PermissionError:
|
||||
raise Forbidden(_("You don't have permission to access this file"))
|
||||
|
||||
return send_private_file(path.split("/private", 1)[1])
|
||||
|
||||
|
||||
def send_private_file(path):
|
||||
path = os.path.join(frappe.local.conf.get('private_path', 'private'), path.strip("/"))
|
||||
|
||||
|
|
@ -127,7 +139,10 @@ def send_private_file(path):
|
|||
raise NotFound
|
||||
|
||||
response = Response(wrap_file(frappe.local.request.environ, f))
|
||||
response.headers.add(b'Content-Disposition', 'attachment', filename=filename.encode("utf-8"))
|
||||
|
||||
# no need for content disposition, let browser handle it
|
||||
# response.headers.add(b'Content-Disposition', 'attachment', filename=filename.encode("utf-8"))
|
||||
|
||||
response.headers[b'Content-Type'] = mimetypes.guess_type(filename)[0] or b'application/octet-stream'
|
||||
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ requests
|
|||
celery
|
||||
redis
|
||||
selenium
|
||||
pdfkit
|
||||
-e git+http://github.com/frappe/python-pdfkit.git#egg=pdfkit
|
||||
babel
|
||||
ipython
|
||||
html2text
|
||||
|
|
|
|||
26
setup.py
26
setup.py
|
|
@ -1,18 +1,18 @@
|
|||
from setuptools import setup, find_packages
|
||||
from pip.req import parse_requirements
|
||||
|
||||
version = "6.12.4"
|
||||
|
||||
with open("requirements.txt", "r") as f:
|
||||
install_requires = f.readlines()
|
||||
version = "6.12.3"
|
||||
requirements = parse_requirements("requirements.txt", session="")
|
||||
|
||||
setup(
|
||||
name='frappe',
|
||||
version=version,
|
||||
description='Metadata driven, full-stack web framework',
|
||||
author='Frappe Technologies',
|
||||
author_email='info@frappe.io',
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
install_requires=install_requires
|
||||
name='frappe',
|
||||
version=version,
|
||||
description='Metadata driven, full-stack web framework',
|
||||
author='Frappe Technologies',
|
||||
author_email='info@frappe.io',
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
install_requires=[str(ir.req) for ir in requirements],
|
||||
dependency_links=[str(ir._link) for ir in requirements if ir._link]
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue