diff --git a/frappe/__init__.py b/frappe/__init__.py index 536e721048..88ced27699 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -469,11 +469,14 @@ def get_precision(doctype, fieldname, currency=None, doc=None): from frappe.model.meta import get_field_precision 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.""" import hashlib, time 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(): """Reset `metadata_version` (Client (Javascript) build ID) hash.""" diff --git a/frappe/core/doctype/file/file.json b/frappe/core/doctype/file/file.json index c9ff215cb9..69ae60e181 100644 --- a/frappe/core/doctype/file/file.json +++ b/frappe/core/doctype/file/file.json @@ -2,7 +2,7 @@ "allow_copy": 0, "allow_import": 1, "allow_rename": 0, - "autoname": "hash", + "autoname": "", "creation": "2012-12-12 11:19:22", "custom": 0, "docstatus": 0, @@ -205,6 +205,28 @@ "set_only_once": 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, "bold": 0, @@ -433,7 +455,7 @@ "is_submittable": 0, "issingle": 0, "istable": 0, - "modified": "2015-09-18 06:22:10.902847", + "modified": "2015-10-07 05:52:52.922698", "modified_by": "Administrator", "module": "Core", "name": "File", diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index ccfaa3a0b4..47c316ee34 100644 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -40,7 +40,7 @@ class File(NestedSet): # home self.name = self.file_name else: - self.name = self.file_url + self.name = frappe.generate_hash("", 10) def after_insert(self): 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 [] def validate(self): - self.validate_duplicate_entry() + if self.is_new(): + self.validate_duplicate_entry() self.validate_folder() self.set_folder_size() @@ -92,6 +93,8 @@ class File(NestedSet): def validate_duplicate_entry(self): if not self.flags.ignore_duplicate_entry_error and not self.is_folder: + # check duplicate name + # check duplicate assignement n_records = frappe.db.sql("""select name from `tabFile` where content_hash=%s @@ -111,6 +114,50 @@ class File(NestedSet): super(File, self).on_trash() 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): self.update_parent_folder_size() @@ -178,7 +225,10 @@ def create_new_folder(file_name, folder): @frappe.whitelist() 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) # recalculate sizes diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 824ab454fd..646b846fde 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe 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.core.doctype.file.file import move_file import json @@ -15,27 +15,19 @@ class TestFile(unittest.TestCase): def setUp(self): self.delete_test_data() self.upload_file() - + 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") if file_name: file = frappe.get_doc("File", file_name) - ancestors = file.get_ancestors() 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): self.saved_file = save_file('file_copy.txt', "Testing file copy example.",\ "", "", self.get_folder("Test Folder 1", "Home").name) self.saved_filename = get_files_path(self.saved_file.file_name) - + def get_folder(self, folder_name, parent_folder="Home"): return frappe.get_doc({ "doctype": "File", @@ -46,61 +38,57 @@ class TestFile(unittest.TestCase): def tests_after_upload(self): self.assertEqual(self.saved_file.folder, _("Home/Test Folder 1")) - + 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") - + self.assertEqual(folder_size, saved_file_size) - + def test_file_copy(self): 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(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) - + def test_folder_copy(self): folder = self.get_folder("Test Folder 2", "Home") 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) - - 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(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) - + def test_non_parent_folder(self): d = frappe.get_doc({ "doctype": "File", "file_name": _("Test_Folder"), "is_folder": 1 }) - + self.assertRaises(frappe.ValidationError, d.save) - + 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() - + 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") 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") self.assertRaises(frappe.ValidationError, folder.delete) - \ No newline at end of file diff --git a/frappe/model/naming.py b/frappe/model/naming.py index c23bd3d633..292e80ce9b 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -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 """ if key=="hash": - return frappe.generate_hash(doctype)[:10] + return frappe.generate_hash(doctype, 10) if not "#" in key: key = key + ".#####" diff --git a/frappe/utils/data.py b/frappe/utils/data.py index f69a9d222f..9ad4afdfc7 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -513,6 +513,9 @@ def get_url(uri=None, full_address=False): """get app url from request""" host_name = frappe.local.conf.host_name + if uri and (uri.startswith("http://") or uri.startswith("https://")): + return uri + if not host_name: 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://' diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 6b62b0bd54..2a81043606 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -246,17 +246,18 @@ def delete_file_data_content(doc): method(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): f = frappe.db.sql("""select file_name from `tabFile` diff --git a/requirements.txt b/requirements.txt index 1777583ba0..41bbfb8b6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,4 @@ click num2words watchdog==0.8.0 bleach +Pillow