Просмотр исходного кода

[feature] Private files. Fixes #927

- Option during upload + all new incoming email files will be private
- Paired with @rmehta
version-14
Anand Doshi 9 лет назад
Родитель
Сommit
8a5addaae7
36 измененных файлов: 488 добавлений и 211 удалений
  1. +0
    -1
      .travis.yml
  2. +3
    -0
      frappe/app.py
  3. +4
    -0
      frappe/change_log/current/private_files.md
  4. +5
    -3
      frappe/commands.py
  5. +5
    -1
      frappe/core/doctype/file/file.js
  6. +47
    -1
      frappe/core/doctype/file/file.json
  7. +12
    -0
      frappe/core/doctype/file/file.py
  8. +15
    -6
      frappe/core/doctype/file/file_list.js
  9. +4
    -4
      frappe/desk/form/load.py
  10. +1
    -0
      frappe/desk/page/backups/backups.py
  11. +3
    -3
      frappe/docs/current/api/utils/frappe.utils.backups.html
  12. +5
    -5
      frappe/docs/current/api/utils/frappe.utils.file_manager.html
  13. +16
    -0
      frappe/docs/current/api/utils/frappe.utils.response.html
  14. +1
    -1
      frappe/docs/current/index.html
  15. +49
    -20
      frappe/docs/current/models/core/file.html
  16. +16
    -0
      frappe/docs/current/models/integrations/dropbox_backup.html
  17. +1
    -1
      frappe/email/receive.py
  18. +25
    -10
      frappe/integrations/doctype/dropbox_backup/dropbox_backup.py
  19. +1
    -1
      frappe/public/css/docs.css
  20. +2
    -1
      frappe/public/js/frappe/form/control.js
  21. +3
    -1
      frappe/public/js/frappe/form/footer/attachments.js
  22. +5
    -2
      frappe/public/js/frappe/form/footer/timeline_item.html
  23. +4
    -0
      frappe/public/js/frappe/ui/dialog.js
  24. +2
    -1
      frappe/public/js/frappe/ui/messages.js
  25. +21
    -15
      frappe/public/js/frappe/ui/upload.html
  26. +98
    -64
      frappe/public/js/frappe/upload.js
  27. +2
    -1
      frappe/public/js/legacy/form.js
  28. +1
    -1
      frappe/public/less/docs.less
  29. +6
    -0
      frappe/public/less/sidebar.less
  30. +2
    -2
      frappe/utils/__init__.py
  31. +38
    -19
      frappe/utils/backups.py
  32. +55
    -29
      frappe/utils/file_manager.py
  33. +5
    -2
      frappe/utils/pdf.py
  34. +17
    -2
      frappe/utils/response.py
  35. +1
    -1
      requirements.txt
  36. +13
    -13
      setup.py

+ 0
- 1
.travis.yml Просмотреть файл

@@ -14,7 +14,6 @@ install:
- sudo apt-get purge -y mysql-common
- wget https://raw.githubusercontent.com/frappe/bench/master/install_scripts/setup_frappe.sh
- sudo bash setup_frappe.sh --skip-setup-bench --mysql-root-password travis
- sudo pip install --upgrade pip
- rm $TRAVIS_BUILD_DIR/.git/shallow
- cd ~/ && bench init frappe-bench --frappe-path $TRAVIS_BUILD_DIR
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/


+ 3
- 0
frappe/app.py Просмотреть файл

@@ -72,6 +72,9 @@ def application(request):
elif frappe.request.path.startswith('/backups'):
response = frappe.utils.response.download_backup(request.path)

elif frappe.request.path.startswith('/private/files/'):
response = frappe.utils.response.download_private_file(request.path)

elif frappe.local.request.method in ('GET', 'HEAD'):
response = frappe.website.render.render(request.path)



+ 4
- 0
frappe/change_log/current/private_files.md Просмотреть файл

@@ -0,0 +1,4 @@
- Attachments can now be marked as **Private**
- Private files cannot be accessed unless you are logged in
- To access a private file, you need to have read permission on the file or read permission on the document to which the file is attached
- All attachments in a new incoming email are private

+ 5
- 3
frappe/commands.py Просмотреть файл

@@ -868,21 +868,23 @@ def use(site, sites_path='.'):
@click.command('backup')
@click.option('--with-files', default=False, is_flag=True, help="Take backup with files")
@pass_context
def backup(context, with_files=False, backup_path_db=None, backup_path_files=None, quiet=False):
def backup(context, with_files=False, backup_path_db=None, backup_path_files=None,
backup_path_private_files=None, quiet=False):
"Backup"
from frappe.utils.backups import scheduled_backup
verbose = context.verbose
for site in context.sites:
frappe.init(site=site)
frappe.connect()
odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, force=True)
odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, backup_path_private_files=backup_path_private_files, force=True)
if verbose:
from frappe.utils import now
print "database backup taken -", odb.backup_path_db, "- on", now()
if with_files:
print "files backup taken -", odb.backup_path_files, "- on", now()
frappe.destroy()
print "private files backup taken -", odb.backup_path_private_files, "- on", now()

frappe.destroy()

@click.command('remove-from-installed-apps')
@click.argument('app')


+ 5
- 1
frappe/core/doctype/file/file.js Просмотреть файл

@@ -1,7 +1,11 @@
frappe.ui.form.on("File", "refresh", function(frm) {
if(!frm.doc.is_folder) {
frm.add_custom_button(__('Download'), function() {
window.open(frm.doc.file_url);
var file_url = frm.doc.file_url;
if (frm.doc.file_name) {
file_url = file_url.replace(/#/g, '%23');
}
window.open(file_url);
}, "icon-download");
}



+ 47
- 1
frappe/core/doctype/file/file.json Просмотреть файл

@@ -25,6 +25,7 @@
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
@@ -32,6 +33,31 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "is_private",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Private",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -48,6 +74,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -71,6 +98,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -93,6 +121,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -117,6 +146,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -140,6 +170,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -162,6 +193,7 @@
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
@@ -184,6 +216,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -207,6 +240,7 @@
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
@@ -230,6 +264,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
@@ -254,6 +289,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
@@ -277,6 +313,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
@@ -300,6 +337,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -323,6 +361,7 @@
"options": "DocType",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
@@ -345,6 +384,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -367,6 +407,7 @@
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
@@ -389,6 +430,7 @@
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -412,6 +454,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -435,6 +478,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -458,6 +502,7 @@
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -476,7 +521,8 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:29:47.191098",
"menu_index": 0,
"modified": "2015-12-08 05:03:48.767257",
"modified_by": "Administrator",
"module": "Core",
"name": "File",


+ 12
- 0
frappe/core/doctype/file/file.py Просмотреть файл

@@ -108,6 +108,9 @@ class File(NestedSet):
frappe.throw(_("Folder is mandatory"))

def validate_file(self):
"""Validates existence of public file
TODO: validate for private file
"""
if (self.file_url or "").startswith("/files/"):
if not self.file_name:
self.file_name = self.file_url.split("/files/")[-1]
@@ -337,3 +340,12 @@ def get_web_image(file_url):
filename = "/files/" + strip(urllib.unquote(filename))

return image, filename, extn

def check_file_permission(file_url):
for file in frappe.get_all("File", filters={"file_url": file_url, "is_private": 1}, fields=["name", "attached_to_doctype", "attached_to_name"]):

if (frappe.has_permission("File", ptype="read", doc=file.name)
or frappe.has_permission(file.attached_to_doctype, ptype="read", doc=file.attached_to_name)):
return True

raise frappe.PermissionError

+ 15
- 6
frappe/core/doctype/file/file_list.js Просмотреть файл

@@ -3,7 +3,7 @@ frappe.provide("frappe.ui");
frappe.listview_settings['File'] = {
hide_name_column: true,
use_route: true,
add_fields: ["is_folder", "file_name", "file_url", "folder"],
add_fields: ["is_folder", "file_name", "file_url", "folder", "is_private"],
formatters: {
file_size: function(value) {
// formatter for file size
@@ -17,13 +17,20 @@ frappe.listview_settings['File'] = {
},
prepare_data: function(data) {
// set image icons
var icon = ""

if(data.is_folder) {
data._title = '<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){


+ 4
- 4
frappe/desk/form/load.py Просмотреть файл

@@ -100,7 +100,7 @@ def get_user_permissions(meta):
return out

def get_attachments(dt, dn):
return frappe.get_all("File", fields=["name", "file_name", "file_url"],
return frappe.get_all("File", fields=["name", "file_name", "file_url", "is_private"],
filters = {"attached_to_name": dn, "attached_to_doctype": dt})

def get_comments(dt, dn, limit=100):
@@ -121,11 +121,11 @@ def get_comments(dt, dn, limit=100):
as_dict=True)

for c in communications:
c.attachments = json.dumps([f.file_url for f in frappe.get_all("File",
fields=["file_url"],
c.attachments = json.dumps(frappe.get_all("File",
fields=["file_url", "is_private"],
filters={"attached_to_doctype": "Communication",
"attached_to_name": c.name}
)])
))

return comments + communications



+ 1
- 0
frappe/desk/page/backups/backups.py Просмотреть файл

@@ -20,5 +20,6 @@ def get_context(context):
files = [('/backups/' + _file,
get_time(os.path.join(path, _file)),
get_size(os.path.join(path, _file))) for _file in files]
files.sort(key=lambda x: x[1], reverse=True)

return {"files": files}

+ 3
- 3
frappe/docs/current/api/utils/frappe.utils.backups.html Просмотреть файл

@@ -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


+ 5
- 5
frappe/docs/current/api/utils/frappe.utils.file_manager.html Просмотреть файл

@@ -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>


+ 16
- 0
frappe/docs/current/api/utils/frappe.utils.response.html Просмотреть файл

@@ -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>


+ 1
- 1
frappe/docs/current/index.html Просмотреть файл

@@ -35,7 +35,7 @@
Version
</td>
<td>
<code>6.12.3</code>
<code>6.12.4</code>
</td>
</tr>
</table>


+ 49
- 20
frappe/docs/current/models/core/file.html Просмотреть файл

@@ -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>


+ 16
- 0
frappe/docs/current/models/integrations/dropbox_backup.html Просмотреть файл

@@ -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>





+ 1
- 1
frappe/email/receive.py Просмотреть файл

@@ -355,7 +355,7 @@ class Email:
for attachment in self.attachments:
try:
file_data = save_file(attachment['fname'], attachment['fcontent'],
doc.doctype, doc.name)
doc.doctype, doc.name, is_private=1)
saved_attachments.append(file_data)

if attachment['fname'] in self.cid_map:


+ 25
- 10
frappe/integrations/doctype/dropbox_backup/dropbox_backup.py Просмотреть файл

@@ -102,10 +102,12 @@ def dropbox_callback(oauth_token=None, not_approved=False):
frappe.db.set_value("Dropbox Backup", "Dropbox Backup", "dropbox_access_allowed", allowed)
frappe.db.set_value("Dropbox Backup", "Dropbox Backup", "send_backups_to_dropbox", 1)
dropbox_client = client.DropboxClient(sess)
try:
dropbox_client.file_create_folder("files")
except:
pass
# try:
# dropbox_client.file_create_folder("private")
# dropbox_client.file_create_folder("private/files")
# dropbox_client.file_create_folder("files")
# except:
# pass

else:
allowed = 0
@@ -144,12 +146,27 @@ def backup_to_dropbox():
upload_file_to_dropbox(filename, "/database", dropbox_client)

frappe.db.close()
response = dropbox_client.metadata("/files")

# upload files to files folder
did_not_upload = []
error_log = []
path = get_files_path()

upload_from_folder(get_files_path(), "/files", dropbox_client, did_not_upload, error_log)
upload_from_folder(get_files_path(is_private=1), "/private/files", dropbox_client, did_not_upload, error_log)

frappe.connect()
return did_not_upload, list(set(error_log))

def upload_from_folder(path, dropbox_folder, dropbox_client, did_not_upload, error_log):
import dropbox.rest

try:
response = dropbox_client.metadata(dropbox_folder)
except dropbox.rest.ErrorResponse, e:
# folder not found
if e.status==404:
response = {"contents": []}

for filename in os.listdir(path):
filename = cstr(filename)

@@ -162,16 +179,14 @@ def backup_to_dropbox():
if os.path.basename(filepath) == os.path.basename(file_metadata["path"]) and os.stat(filepath).st_size == int(file_metadata["bytes"]):
found = True
break

if not found:
try:
upload_file_to_dropbox(filepath, "/files", dropbox_client)
upload_file_to_dropbox(filepath, dropbox_folder, dropbox_client)
except Exception:
did_not_upload.append(filename)
error_log.append(frappe.get_traceback())

frappe.connect()
return did_not_upload, list(set(error_log))

def get_dropbox_session():
try:
from dropbox import session


+ 1
- 1
frappe/public/css/docs.css Просмотреть файл

@@ -516,7 +516,7 @@ p {
.fake-browser-frame {
position: relative;
margin: 24px auto 0px;
box-shadow: 0px -6px 100px 1px rgba(0, 0, 0, 0.1);
box-shadow: 0px -6px 100px 1px rgba(0, 0, 0, 0.1), 0px -6px 50px 1px rgba(0, 0, 0, 0.4);
}
.fake-browser-frame::before {
content: "";


+ 2
- 1
frappe/public/js/frappe/form/control.js Просмотреть файл

@@ -810,13 +810,14 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
onerror: function() {
me.dialog.hide();
},
is_private: this.df.is_private
}

if(this.frm) {
this.upload_options.args = {
from_form: 1,
doctype: this.frm.doctype,
docname: this.frm.docname,
docname: this.frm.docname
}
} else {
this.upload_options.on_attach = function(fileobj, dataurl) {


+ 3
- 1
frappe/public/js/frappe/form/footer/attachments.js Просмотреть файл

@@ -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">&times;</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(),


+ 5
- 2
frappe/public/js/frappe/form/footer/timeline_item.html Просмотреть файл

@@ -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>
{% }); %}


+ 4
- 0
frappe/public/js/frappe/ui/dialog.js Просмотреть файл

@@ -30,6 +30,10 @@ frappe.ui.Dialog = frappe.ui.FieldGroup.extend({
this.set_primary_action(this.primary_action_label || __("Submit"), this.primary_action);
}

if (this.secondary_action_label) {
this.get_close_btn().html(this.secondary_action_label);
}

var me = this;
this.$wrapper
.on("hide.bs.modal", function() {


+ 2
- 1
frappe/public/js/frappe/ui/messages.js Просмотреть файл

@@ -28,7 +28,8 @@ frappe.confirm = function(message, ifyes, ifno) {
primary_action: function() {
ifyes();
d.hide();
}
},
secondary_action_label: __("No")
});
d.show();



+ 21
- 15
frappe/public/js/frappe/ui/upload.html Просмотреть файл

@@ -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>

+ 98
- 64
frappe/public/js/frappe/upload.js Просмотреть файл

@@ -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(',');


+ 2
- 1
frappe/public/js/legacy/form.js Просмотреть файл

@@ -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
});
});
}


+ 1
- 1
frappe/public/less/docs.less Просмотреть файл

@@ -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 {


+ 6
- 0
frappe/public/less/sidebar.less Просмотреть файл

@@ -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;
}


+ 2
- 2
frappe/utils/__init__.py Просмотреть файл

@@ -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__), '..', '..', '..'))


+ 38
- 19
frappe/utils/backups.py Просмотреть файл

@@ -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



+ 55
- 29
frappe/utils/file_manager.py Просмотреть файл

@@ -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



+ 5
- 2
frappe/utils/pdf.py Просмотреть файл

@@ -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()


+ 17
- 2
frappe/utils/response.py Просмотреть файл

@@ -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


+ 1
- 1
requirements.txt Просмотреть файл

@@ -21,7 +21,7 @@ requests
celery
redis
selenium
pdfkit
-e git+http://github.com/frappe/python-pdfkit.git#egg=pdfkit
babel
ipython
html2text


+ 13
- 13
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]
)

Загрузка…
Отмена
Сохранить