[enhancement] added 'make_thumbnail' to File and will be saved as thumbnail_urlversion-14
@@ -469,11 +469,14 @@ def get_precision(doctype, fieldname, currency=None, doc=None): | |||||
from frappe.model.meta import get_field_precision | from frappe.model.meta import get_field_precision | ||||
return get_field_precision(get_meta(doctype).get_field(fieldname), doc, currency) | return get_field_precision(get_meta(doctype).get_field(fieldname), doc, currency) | ||||
def generate_hash(txt=None): | |||||
def generate_hash(txt=None, length=None): | |||||
"""Generates random hash for given text + current timestamp + random string.""" | """Generates random hash for given text + current timestamp + random string.""" | ||||
import hashlib, time | import hashlib, time | ||||
from .utils import random_string | from .utils import random_string | ||||
return hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest() | |||||
digest = hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest() | |||||
if length: | |||||
digest = digest[:length] | |||||
return digest | |||||
def reset_metadata_version(): | def reset_metadata_version(): | ||||
"""Reset `metadata_version` (Client (Javascript) build ID) hash.""" | """Reset `metadata_version` (Client (Javascript) build ID) hash.""" | ||||
@@ -2,7 +2,7 @@ | |||||
"allow_copy": 0, | "allow_copy": 0, | ||||
"allow_import": 1, | "allow_import": 1, | ||||
"allow_rename": 0, | "allow_rename": 0, | ||||
"autoname": "hash", | |||||
"autoname": "", | |||||
"creation": "2012-12-12 11:19:22", | "creation": "2012-12-12 11:19:22", | ||||
"custom": 0, | "custom": 0, | ||||
"docstatus": 0, | "docstatus": 0, | ||||
@@ -205,6 +205,28 @@ | |||||
"set_only_once": 0, | "set_only_once": 0, | ||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "thumbnail_url", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Thumbnail URL", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | { | ||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
@@ -433,7 +455,7 @@ | |||||
"is_submittable": 0, | "is_submittable": 0, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"modified": "2015-09-18 06:22:10.902847", | |||||
"modified": "2015-10-07 05:52:52.922698", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "File", | "name": "File", | ||||
@@ -40,7 +40,7 @@ class File(NestedSet): | |||||
# home | # home | ||||
self.name = self.file_name | self.name = self.file_name | ||||
else: | else: | ||||
self.name = self.file_url | |||||
self.name = frappe.generate_hash("", 10) | |||||
def after_insert(self): | def after_insert(self): | ||||
self.update_parent_folder_size() | self.update_parent_folder_size() | ||||
@@ -53,7 +53,8 @@ class File(NestedSet): | |||||
return frappe.db.sql_list("select name from tabFile where folder='%s'"%self.name) or [] | return frappe.db.sql_list("select name from tabFile where folder='%s'"%self.name) or [] | ||||
def validate(self): | def validate(self): | ||||
self.validate_duplicate_entry() | |||||
if self.is_new(): | |||||
self.validate_duplicate_entry() | |||||
self.validate_folder() | self.validate_folder() | ||||
self.set_folder_size() | self.set_folder_size() | ||||
@@ -92,6 +93,8 @@ class File(NestedSet): | |||||
def validate_duplicate_entry(self): | def validate_duplicate_entry(self): | ||||
if not self.flags.ignore_duplicate_entry_error and not self.is_folder: | if not self.flags.ignore_duplicate_entry_error and not self.is_folder: | ||||
# check duplicate name | |||||
# check duplicate assignement | # check duplicate assignement | ||||
n_records = frappe.db.sql("""select name from `tabFile` | n_records = frappe.db.sql("""select name from `tabFile` | ||||
where content_hash=%s | where content_hash=%s | ||||
@@ -111,6 +114,50 @@ class File(NestedSet): | |||||
super(File, self).on_trash() | super(File, self).on_trash() | ||||
self.delete_file() | self.delete_file() | ||||
def make_thumbnail(self): | |||||
from PIL import Image, ImageOps | |||||
import os | |||||
if self.file_url: | |||||
if self.file_url.startswith("/files"): | |||||
try: | |||||
image = Image.open(frappe.get_site_path("public", self.file_url)) | |||||
filename, extn = self.file_url.rsplit(".", 1) | |||||
except IOError: | |||||
frappe.msgprint("Unable to read file format for {0}".format(self.file_url)) | |||||
else: | |||||
# downlaod | |||||
import requests, StringIO | |||||
file_url = frappe.utils.get_url(self.file_url) | |||||
r = requests.get(file_url, stream=True) | |||||
r.raise_for_status() | |||||
image = Image.open(StringIO.StringIO(r.content)) | |||||
filename, extn = self.file_url.rsplit("/", 1)[1].rsplit(".", 1) | |||||
filename = "/files/" + filename | |||||
thumbnail = ImageOps.fit( | |||||
image, | |||||
(300, 300), | |||||
Image.ANTIALIAS | |||||
) | |||||
thumbnail_url = filename + "_small." + extn | |||||
if thumbnail_url[0]=="/": | |||||
thumbnail_url = thumbnail_url[1:] | |||||
path = os.path.abspath(frappe.get_site_path("public", thumbnail_url)) | |||||
try: | |||||
thumbnail.save(path) | |||||
self.db_set("thumbnail_url", thumbnail_url) | |||||
except IOError: | |||||
frappe.msgprint("Unable to write file format for {0}".format(path)) | |||||
return thumbnail_url | |||||
def after_delete(self): | def after_delete(self): | ||||
self.update_parent_folder_size() | self.update_parent_folder_size() | ||||
@@ -178,7 +225,10 @@ def create_new_folder(file_name, folder): | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def move_file(file_list, new_parent, old_parent): | def move_file(file_list, new_parent, old_parent): | ||||
for file_obj in json.loads(file_list): | |||||
if isinstance(file_list, basestring): | |||||
file_list = json.loads(file_list) | |||||
for file_obj in file_list: | |||||
setup_folder_path(file_obj.get("name"), new_parent) | setup_folder_path(file_obj.get("name"), new_parent) | ||||
# recalculate sizes | # recalculate sizes | ||||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals | |||||
import frappe | import frappe | ||||
import unittest | import unittest | ||||
from frappe.utils.file_manager import save_file, get_file, get_files_path | |||||
from frappe.utils.file_manager import save_file, get_files_path | |||||
from frappe import _ | from frappe import _ | ||||
from frappe.core.doctype.file.file import move_file | from frappe.core.doctype.file.file import move_file | ||||
import json | import json | ||||
@@ -15,27 +15,19 @@ class TestFile(unittest.TestCase): | |||||
def setUp(self): | def setUp(self): | ||||
self.delete_test_data() | self.delete_test_data() | ||||
self.upload_file() | self.upload_file() | ||||
def delete_test_data(self): | def delete_test_data(self): | ||||
for file_name in ["folder_copy.txt", "file_copy.txt", "Test Folder 2"]: | |||||
for file_name in ["folder_copy.txt", "file_copy.txt", "Test Folder 3", "Test Folder 2", "Test Folder 1"]: | |||||
file_name = frappe.db.get_value("File", {"file_name": file_name}, "name") | file_name = frappe.db.get_value("File", {"file_name": file_name}, "name") | ||||
if file_name: | if file_name: | ||||
file = frappe.get_doc("File", file_name) | file = frappe.get_doc("File", file_name) | ||||
ancestors = file.get_ancestors() | |||||
file.delete() | file.delete() | ||||
self.delete_ancestors(ancestors) | |||||
def delete_ancestors(self, ancestors): | |||||
for folder in ancestors: | |||||
if folder != "Home": | |||||
folder = frappe.get_doc("File", folder) | |||||
folder.delete() | |||||
def upload_file(self): | def upload_file(self): | ||||
self.saved_file = save_file('file_copy.txt', "Testing file copy example.",\ | self.saved_file = save_file('file_copy.txt', "Testing file copy example.",\ | ||||
"", "", self.get_folder("Test Folder 1", "Home").name) | "", "", self.get_folder("Test Folder 1", "Home").name) | ||||
self.saved_filename = get_files_path(self.saved_file.file_name) | self.saved_filename = get_files_path(self.saved_file.file_name) | ||||
def get_folder(self, folder_name, parent_folder="Home"): | def get_folder(self, folder_name, parent_folder="Home"): | ||||
return frappe.get_doc({ | return frappe.get_doc({ | ||||
"doctype": "File", | "doctype": "File", | ||||
@@ -46,61 +38,57 @@ class TestFile(unittest.TestCase): | |||||
def tests_after_upload(self): | def tests_after_upload(self): | ||||
self.assertEqual(self.saved_file.folder, _("Home/Test Folder 1")) | self.assertEqual(self.saved_file.folder, _("Home/Test Folder 1")) | ||||
folder_size = frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size") | folder_size = frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size") | ||||
saved_file_size = frappe.db.get_value("File", self.saved_file.name, "file_size") | saved_file_size = frappe.db.get_value("File", self.saved_file.name, "file_size") | ||||
self.assertEqual(folder_size, saved_file_size) | self.assertEqual(folder_size, saved_file_size) | ||||
def test_file_copy(self): | def test_file_copy(self): | ||||
folder = self.get_folder("Test Folder 2", "Home") | folder = self.get_folder("Test Folder 2", "Home") | ||||
file = frappe.get_doc("File", "/files/file_copy.txt") | |||||
file_dict = [{"name": file.name}] | |||||
move_file(json.dumps(file_dict), folder.name, file.folder) | |||||
file = frappe.get_doc("File", "/files/file_copy.txt") | |||||
file = frappe.get_doc("File", {"file_name":"file_copy.txt"}) | |||||
move_file([{"name": file.name}], folder.name, file.folder) | |||||
file = frappe.get_doc("File", {"file_name":"file_copy.txt"}) | |||||
self.assertEqual(_("Home/Test Folder 2"), file.folder) | self.assertEqual(_("Home/Test Folder 2"), file.folder) | ||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), file.file_size) | self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), file.file_size) | ||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), None) | self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), None) | ||||
def test_folder_copy(self): | def test_folder_copy(self): | ||||
folder = self.get_folder("Test Folder 2", "Home") | folder = self.get_folder("Test Folder 2", "Home") | ||||
folder = self.get_folder("Test Folder 3", "Home/Test Folder 2") | folder = self.get_folder("Test Folder 3", "Home/Test Folder 2") | ||||
self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name) | self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name) | ||||
file_dict = [{"name": folder.name}] | |||||
move_file(json.dumps(file_dict), 'Home/Test Folder 1', folder.folder) | |||||
file = frappe.get_doc("File", "/files/folder_copy.txt") | |||||
move_file([{"name": folder.name}], 'Home/Test Folder 1', folder.folder) | |||||
file = frappe.get_doc("File", {"file_name":"folder_copy.txt"}) | |||||
file_copy_txt = frappe.get_value("File", {"file_name":"file_copy.txt"}) | |||||
if file_copy_txt: | |||||
frappe.get_doc("File", file_copy_txt).delete() | |||||
self.assertEqual(_("Home/Test Folder 1/Test Folder 3"), file.folder) | self.assertEqual(_("Home/Test Folder 1/Test Folder 3"), file.folder) | ||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), file.file_size) | self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), file.file_size) | ||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), None) | self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), None) | ||||
def test_non_parent_folder(self): | def test_non_parent_folder(self): | ||||
d = frappe.get_doc({ | d = frappe.get_doc({ | ||||
"doctype": "File", | "doctype": "File", | ||||
"file_name": _("Test_Folder"), | "file_name": _("Test_Folder"), | ||||
"is_folder": 1 | "is_folder": 1 | ||||
}) | }) | ||||
self.assertRaises(frappe.ValidationError, d.save) | self.assertRaises(frappe.ValidationError, d.save) | ||||
def test_on_delete(self): | def test_on_delete(self): | ||||
file = frappe.get_doc("File", "/files/file_copy.txt") | |||||
file = frappe.get_doc("File", {"file_name":"file_copy.txt"}) | |||||
file.delete() | file.delete() | ||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), None) | self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), None) | ||||
folder = self.get_folder("Test Folder 3", "Home/Test Folder 1") | folder = self.get_folder("Test Folder 3", "Home/Test Folder 1") | ||||
self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name) | self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name) | ||||
folder = frappe.get_doc("File", "Home/Test Folder 1/Test Folder 3") | folder = frappe.get_doc("File", "Home/Test Folder 1/Test Folder 3") | ||||
self.assertRaises(frappe.ValidationError, folder.delete) | self.assertRaises(frappe.ValidationError, folder.delete) | ||||
@@ -82,7 +82,7 @@ def make_autoname(key, doctype=''): | |||||
DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series | DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series | ||||
""" | """ | ||||
if key=="hash": | if key=="hash": | ||||
return frappe.generate_hash(doctype)[:10] | |||||
return frappe.generate_hash(doctype, 10) | |||||
if not "#" in key: | if not "#" in key: | ||||
key = key + ".#####" | key = key + ".#####" | ||||
@@ -513,6 +513,9 @@ def get_url(uri=None, full_address=False): | |||||
"""get app url from request""" | """get app url from request""" | ||||
host_name = frappe.local.conf.host_name | host_name = frappe.local.conf.host_name | ||||
if uri and (uri.startswith("http://") or uri.startswith("https://")): | |||||
return uri | |||||
if not host_name: | if not host_name: | ||||
if hasattr(frappe.local, "request") and frappe.local.request and frappe.local.request.host: | if hasattr(frappe.local, "request") and frappe.local.request and frappe.local.request.host: | ||||
protocol = 'https' == frappe.get_request_header('X-Forwarded-Proto', "") and 'https://' or 'http://' | protocol = 'https' == frappe.get_request_header('X-Forwarded-Proto', "") and 'https://' or 'http://' | ||||
@@ -246,17 +246,18 @@ def delete_file_data_content(doc): | |||||
method(doc) | method(doc) | ||||
def delete_file_from_filesystem(doc): | def delete_file_from_filesystem(doc): | ||||
path = doc.file_name | |||||
if path.startswith("files/"): | |||||
path = frappe.utils.get_site_path("public", doc.file_name) | |||||
else: | |||||
path = frappe.utils.get_site_path("public", "files", doc.file_name) | |||||
path = encode(path) | |||||
if os.path.exists(path): | |||||
os.remove(path) | |||||
"""Delete file, thumbnail from File document""" | |||||
delete_file(doc.file_url) | |||||
delete_file(doc.thumbnail_url) | |||||
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]) | |||||
path = encode(path) | |||||
if os.path.exists(path): | |||||
os.remove(path) | |||||
def get_file(fname): | def get_file(fname): | ||||
f = frappe.db.sql("""select file_name from `tabFile` | f = frappe.db.sql("""select file_name from `tabFile` | ||||
@@ -30,3 +30,4 @@ click | |||||
num2words | num2words | ||||
watchdog==0.8.0 | watchdog==0.8.0 | ||||
bleach | bleach | ||||
Pillow |