- Option during upload + all new incoming email files will be private - Paired with @rmehtaversion-14
@@ -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) | |||
@@ -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(args.file_url) { | |||
frappe.upload._upload_file(fileobj, args, opts); | |||
} else { | |||
frappe.upload.read_file(fileobj, args, opts); | |||
} | |||
}, | |||
if(opts.on_attach) { | |||
opts.on_attach(args, dataurl) | |||
_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 { | |||
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; | |||
} | |||
if ("is_private" in opts) { | |||
args["is_private"] = opts.is_private; | |||
} | |||
// 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); | |||
frappe.upload.upload_to_server(fileobj, args, opts, dataurl); | |||
} | |||
} | |||
}, | |||
if(args.file_url) { | |||
_upload_file(); | |||
} 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; | |||
} | |||
} | |||
read_file: function(fileobj, args, opts) { | |||
var freader = new FileReader(); | |||
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.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); | |||
}, | |||
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 | |||
@@ -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] | |||
) |