Переглянути джерело

Merge pull request #1290 from rmehta/dms

[enhancement] document management system
version-14
Anand Doshi 9 роки тому
джерело
коміт
d01dc8c419
51 змінених файлів з 1431 додано та 417 видалено
  1. +19
    -2
      frappe/__init__.py
  2. +1
    -0
      frappe/boot.py
  3. +9
    -0
      frappe/config/desktop.py
  4. +1
    -1
      frappe/config/setup.py
  5. +0
    -0
      frappe/core/doctype/file/__init__.py
  6. +21
    -0
      frappe/core/doctype/file/file.js
  7. +486
    -0
      frappe/core/doctype/file/file.json
  8. +192
    -0
      frappe/core/doctype/file/file.py
  9. +207
    -0
      frappe/core/doctype/file/file_list.js
  10. +106
    -0
      frappe/core/doctype/file/test_file.py
  11. +0
    -3
      frappe/core/doctype/file_data/README.md
  12. +0
    -4
      frappe/core/doctype/file_data/__init__.py
  13. +0
    -179
      frappe/core/doctype/file_data/file_data.json
  14. +0
    -57
      frappe/core/doctype/file_data/file_data.py
  15. +7
    -3
      frappe/core/doctype/page/page.py
  16. +3
    -2
      frappe/data/Framework.sql
  17. +2
    -2
      frappe/desk/form/load.py
  18. +11
    -7
      frappe/desk/search.py
  19. +2
    -2
      frappe/email/doctype/email_account/test_email_account.py
  20. +2
    -2
      frappe/frappeclient.py
  21. +1
    -0
      frappe/model/delete_doc.py
  22. +4
    -0
      frappe/model/document.py
  23. +4
    -3
      frappe/model/rename_doc.py
  24. +1
    -0
      frappe/patches.txt
  25. +2
    -2
      frappe/patches/v4_0/file_manager_hooks.py
  26. +7
    -7
      frappe/patches/v4_1/file_manager_fix.py
  27. +0
    -0
      frappe/patches/v6_1/__init__.py
  28. +30
    -0
      frappe/patches/v6_1/rename_file_data.py
  29. +37
    -17
      frappe/permissions.py
  30. +9
    -0
      frappe/public/css/desk.css
  31. +41
    -20
      frappe/public/js/frappe/form/footer/attachments.js
  32. +76
    -27
      frappe/public/js/frappe/list/doclistview.js
  33. +7
    -2
      frappe/public/js/frappe/list/list_item_main.html
  34. +2
    -2
      frappe/public/js/frappe/list/list_item_row.html
  35. +2
    -2
      frappe/public/js/frappe/list/list_item_row_head.html
  36. +14
    -8
      frappe/public/js/frappe/list/list_item_subject.html
  37. +5
    -1
      frappe/public/js/frappe/list/listview.js
  38. +4
    -0
      frappe/public/js/frappe/misc/utils.js
  39. +15
    -4
      frappe/public/js/frappe/model/perm.js
  40. +19
    -14
      frappe/public/js/frappe/ui/filters/filters.js
  41. +6
    -2
      frappe/public/js/frappe/ui/listing.js
  42. +6
    -2
      frappe/public/js/frappe/ui/page.js
  43. +1
    -1
      frappe/public/js/frappe/ui/toolbar/awesome_bar.js
  44. +6
    -2
      frappe/public/js/frappe/upload.js
  45. +7
    -4
      frappe/public/js/frappe/views/breadcrumbs.js
  46. +10
    -6
      frappe/public/js/frappe/views/factory.js
  47. +11
    -0
      frappe/public/less/desk.less
  48. +1
    -1
      frappe/utils/csvutils.py
  49. +28
    -26
      frappe/utils/file_manager.py
  50. +3
    -0
      frappe/utils/install.py
  51. +3
    -0
      frappe/utils/nestedset.py

+ 19
- 2
frappe/__init__.py Переглянути файл

@@ -38,9 +38,12 @@ class _dict(dict):
def copy(self): def copy(self):
return _dict(dict(self).copy()) return _dict(dict(self).copy())


def _(msg):
def _(msg, lang=None):
"""Returns translated string in current lang, if exists.""" """Returns translated string in current lang, if exists."""
if local.lang == "en":
if not lang:
lang = local.lang

if lang == "en":
return msg return msg


from frappe.translate import get_full_dict from frappe.translate import get_full_dict
@@ -905,6 +908,20 @@ def get_all(doctype, *args, **kwargs):
kwargs["limit_page_length"] = 0 kwargs["limit_page_length"] = 0
return get_list(doctype, *args, **kwargs) return get_list(doctype, *args, **kwargs)


def get_value(*args, **kwargs):
"""Returns a document property or list of properties.

Alias for `frappe.db.get_value`

:param doctype: DocType name.
:param filters: Filters like `{"x":"y"}` or name of the document. `None` if Single DocType.
:param fieldname: Column name.
:param ignore: Don't raise exception if table, column is missing.
:param as_dict: Return values as dict.
:param debug: Print query in error log.
"""
return db.get_value(*args, **kwargs)

def add_version(doc): def add_version(doc):
"""Insert a new **Version** of the given document. """Insert a new **Version** of the given document.
A **Version** is a JSON dump of the current document state.""" A **Version** is a JSON dump of the current document state."""


+ 1
- 0
frappe/boot.py Переглянути файл

@@ -53,6 +53,7 @@ def get_bootinfo():
load_conf_settings(bootinfo) load_conf_settings(bootinfo)
load_print(bootinfo, doclist) load_print(bootinfo, doclist)
doclist.extend(get_meta_bundle("Page")) doclist.extend(get_meta_bundle("Page"))
bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1})


# ipinfo # ipinfo
if frappe.session['data'].get('ipinfo'): if frappe.session['data'].get('ipinfo'):


+ 9
- 0
frappe/config/desktop.py Переглянути файл

@@ -45,6 +45,15 @@ def get_data():
"link": "List/Note", "link": "List/Note",
"type": "list" "type": "list"
}, },
"File Manager": {
"color": "#905df5",
"doctype": "File",
"icon": "icon-folder-close",
"icon": "octicon octicon-file-directory",
"label": _("File Manager"),
"link": "List/File",
"type": "list"
},
"Website": { "Website": {
"color": "#16a085", "color": "#16a085",
"icon": "icon-globe", "icon": "icon-globe",


+ 1
- 1
frappe/config/setup.py Переглянути файл

@@ -100,7 +100,7 @@ def get_data():
}, },
{ {
"type": "doctype", "type": "doctype",
"name": "File Data",
"name": "File",
"description": _("Manage uploaded files.") "description": _("Manage uploaded files.")
} }
] ]


+ 0
- 0
frappe/core/doctype/file/__init__.py Переглянути файл


+ 21
- 0
frappe/core/doctype/file/file.js Переглянути файл

@@ -0,0 +1,21 @@
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);
}, "icon-download");
}

var wrapper = frm.get_field("preview_html").$wrapper;
var is_viewable = frappe.utils.is_image_file(frm.doc.file_url);

frm.toggle_display("preview", is_viewable);
frm.toggle_display("preview_html", is_viewable);

if(is_viewable){
wrapper.html('<div class="img_preview">\
<img class="img-responsive" src="'+frm.doc.file_url+'"></img>\
</div>');
} else {
wrapper.empty();
}
});

+ 486
- 0
frappe/core/doctype/file/file.json Переглянути файл

@@ -0,0 +1,486 @@
{
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "hash",
"creation": "2012-12-12 11:19:22",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "file_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "File Name",
"no_copy": 0,
"oldfieldname": "file_name",
"oldfieldtype": "Data",
"permlevel": 0,
"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,
"collapsible": 0,
"fieldname": "preview",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Preview",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "preview_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Preview HTML",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "",
"fieldname": "is_home_folder",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Home Folder",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "is_attachments_folder",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Attachments Folder",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "file_size",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "File Size",
"no_copy": 0,
"permlevel": 0,
"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,
"collapsible": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "file_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "File URL",
"no_copy": 0,
"permlevel": 0,
"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,
"collapsible": 0,
"fieldname": "folder",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Folder",
"no_copy": 0,
"options": "File",
"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,
"collapsible": 0,
"fieldname": "is_folder",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Folder",
"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,
"collapsible": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "attached_to_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Attached To DocType",
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "attached_to_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Attached To Name",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "content_hash",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Content Hash",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "lft",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "rgt",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "old_parent",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "old_parent",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-file",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-18 06:22:10.902847",
"modified_by": "Administrator",
"module": "Core",
"name": "File",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"title_field": "file_name"
}

+ 192
- 0
frappe/core/doctype/file/file.py Переглянути файл

@@ -0,0 +1,192 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
"""
record of files

naming for same name files: file.gif, file-1.gif, file-2.gif etc
"""

import frappe, frappe.utils
from frappe.utils.file_manager import delete_file_data_content
from frappe import _

from frappe.utils.nestedset import NestedSet
import json

class FolderNotEmpty(frappe.ValidationError): pass

class File(NestedSet):
nsm_parent_field = 'folder'
no_feed_on_delete = True

def before_insert(self):
frappe.local.rollback_observers.append(self)
self.set_folder_name()
self.set_name()

def get_name_based_on_parent_folder(self):
path = get_breadcrumbs(self.folder)
folder_name = frappe.get_value("File", self.folder, "file_name")
return "/".join([d.file_name for d in path] + [folder_name, self.file_name])

def set_name(self):
"""Set name for folder"""
if self.is_folder:
if self.folder:
self.name = self.get_name_based_on_parent_folder()
else:
# home
self.name = self.file_name
else:
self.name = self.file_url

def after_insert(self):
self.update_parent_folder_size()

def after_rename(self, olddn, newdn, merge=False):
for successor in self.get_successor():
setup_folder_path(successor, self.name)

def get_successor(self):
return frappe.db.sql_list("select name from tabFile where folder='%s'"%self.name) or []

def validate(self):
self.validate_duplicate_entry()
self.validate_folder()
self.set_folder_size()

def set_folder_size(self):
"""Set folder size if folder"""
if self.is_folder and not self.is_new():
self.file_size = self.get_folder_size()
frappe.db.set_value("File", self.name, "file_size", self.file_size)

for folder in self.get_ancestors():
frappe.db.set_value("File", folder, "file_size", self.get_folder_size(folder))

def get_folder_size(self, folder=None):
"""Returns folder size for current folder"""
if not folder:
folder = self.name
file_size = frappe.db.sql("""select sum(ifnull(file_size,0))
from tabFile where folder=%s """, (folder))[0][0]

return file_size

def update_parent_folder_size(self):
"""Update size of parent folder"""
if self.folder and not self.is_folder: # it not home
frappe.get_doc("File", self.folder).save()

def set_folder_name(self):
"""Make parent folders if not exists based on reference doctype and name"""
if self.attached_to_doctype and not self.folder:
self.folder = frappe.db.get_value("File", {"is_attachments_folder": 1})

def validate_folder(self):
if not self.is_home_folder and not self.folder and \
not self.flags.ignore_folder_validate:
frappe.throw(_("Folder is mandatory"))

def validate_duplicate_entry(self):
if not self.flags.ignore_duplicate_entry_error:
# check duplicate assignement
n_records = frappe.db.sql("""select name from `tabFile`
where content_hash=%s
and name!=%s
and attached_to_doctype=%s
and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype,
self.attached_to_name))
if len(n_records) > 0:
self.duplicate_entry = n_records[0][0]
frappe.throw(frappe._("Same file has already been attached to the record"), frappe.DuplicateEntryError)

def on_trash(self):
self.check_folder_is_empty()
self.check_reference_doc_permission()
super(File, self).on_trash()
self.delete_file()

def after_delete(self):
self.update_parent_folder_size()

def check_folder_is_empty(self):
"""Throw exception if folder is not empty"""
if self.is_folder and frappe.get_all("File", filters={"folder": self.name}):
frappe.throw(_("Folder {0} is not empty").format(self.name), FolderNotEmpty)

def check_reference_doc_permission(self):
"""Check if permission exists for reference document"""
if self.attached_to_name:
# check persmission
try:
if not self.flags.ignore_permissions and \
not frappe.has_permission(self.attached_to_doctype,
"write", self.attached_to_name):
frappe.throw(frappe._("No permission to write / remove."),
frappe.PermissionError)
except frappe.DoesNotExistError:
pass

def delete_file(self):
"""If file not attached to any other record, delete it"""
if self.file_name and self.content_hash and (not frappe.db.count("File",
{"content_hash": self.content_hash, "name": ["!=", self.name]})):
delete_file_data_content(self)

def on_rollback(self):
self.on_trash()

def on_doctype_update():
frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"])

def make_home_folder():
home = frappe.get_doc({
"doctype": "File",
"is_folder": 1,
"is_home_folder": 1,
"file_name": _("Home")
}).insert()

frappe.get_doc({
"doctype": "File",
"folder": home.name,
"is_folder": 1,
"is_attachments_folder": 1,
"file_name": _("Attachments")
}).insert()

@frappe.whitelist()
def get_breadcrumbs(folder):
"""returns name, file_name of parent folder"""
lft, rgt = frappe.db.get_value("File", folder, ["lft", "rgt"])
return frappe.db.sql("""select name, file_name from tabFile
where lft < %s and rgt > %s order by lft asc""", (lft, rgt), as_dict=1)

@frappe.whitelist()
def create_new_folder(file_name, folder):
""" create new folder under current parent folder """
file = frappe.new_doc("File")
file.file_name = file_name
file.is_folder = 1
file.folder = folder
file.insert()

@frappe.whitelist()
def move_file(file_list, new_parent, old_parent):
for file_obj in json.loads(file_list):
setup_folder_path(file_obj.get("name"), new_parent)

# recalculate sizes
frappe.get_doc("File", old_parent).save()
frappe.get_doc("File", new_parent).save()

def setup_folder_path(filename, new_parent):
file = frappe.get_doc("File", filename)
file.folder = new_parent
file.save()

if file.is_folder:
frappe.rename_doc("File", file.name, file.get_name_based_on_parent_folder(), ignore_permissions=True)

+ 207
- 0
frappe/core/doctype/file/file_list.js Переглянути файл

@@ -0,0 +1,207 @@
frappe.provide("frappe.ui");

frappe.listview_settings['File'] = {
hide_name_column: true,
use_route: true,
add_fields: ["is_folder", "file_name", "file_url", "folder"],
formatters: {
file_size: function(value) {
// formatter for file size
if(value > 1048576) {
value = flt(flt(value) / 1048576, 1) + "M";
} else if (value > 1024) {
value = flt(flt(value) / 1024, 1) + "K";
}
return value;
}
},
prepare_data: function(data) {
// set image icons
if(data.is_folder) {
data._title = '<i class="icon-folder-close-alt icon-fixed-width"></i> ' + data.file_name;
} else if(frappe.utils.is_image_file(data.file_name)) {
data._title = '<i class="icon-picture icon-fixed-width"></i> ' + data.file_name;
} else {
data._title = '<i class="icon-file-alt icon-fixed-width"></i> \
' + (data.file_name ? data.file_name : data.file_url);
}
},
onload: function(doclist) {
doclist.filter_area = doclist.wrapper.find(".show_filters");

doclist.breadcrumb = $('<ol class="breadcrumb for-file-list"></ol>')
.insertBefore(doclist.filter_area);

doclist.listview.settings.setup_menu(doclist);
doclist.listview.settings.setup_dragdrop(doclist);

doclist.$page.on("click", ".list-delete", function(event) {
doclist.listview.settings.add_menu_item_copy(doclist);
})
},
list_view_doc:function(doclist){
$(doclist.wrapper).on("click", 'button[list_view_doc="'+doclist.doctype+'"]', function(){
dialog = frappe.ui.get_upload_dialog({
"args": {
"folder": doclist.current_folder,
"from_form": 1
},
callback: function() {
doclist.refresh();
}
});
});
},
setup_menu: function(doclist) {
doclist.page.add_menu_item(__("New Folder"), function() {
var d = frappe.prompt(__("Name"), function(values) {
if((values.value.indexOf("/") > -1)){
frappe.throw("Folder name should not include / !!!");
return;
}
var data = {
"file_name": values.value,
"folder": doclist.current_folder
};
frappe.call({
method: "frappe.core.doctype.file.file.create_new_folder",
args: data,
callback: function(r) { }
})
}, __('Enter folder name'), __("Create"));
});

doclist.page.add_menu_item(__("Edit Folder"), function() {
frappe.set_route("Form", "File", doclist.current_folder);
});
},
setup_dragdrop: function(doclist) {
$(doclist.$page).on('dragenter dragover', false)
.on('drop', function (e) {
var dataTransfer = e.originalEvent.dataTransfer;
if (!(dataTransfer && dataTransfer.files && dataTransfer.files.length > 0)) {
return;
}
e.stopPropagation();
e.preventDefault();
frappe.upload.upload_file(dataTransfer.files[0], {
"folder": doclist.current_folder,
"from_form": 1
}, {});
});
},
add_menu_item_copy: function(doclist){
if (!doclist.copy) {
var copy_menu = doclist.page.add_menu_item(__("Copy"), function() {
if(doclist.$page.find(".list-delete:checked").length){
doclist.selected_files = doclist.get_checked_items();
doclist.old_parent = doclist.current_folder;
doclist.listview.settings.add_menu_item_paste(doclist);
}
else{
frappe.throw("Please select file to copy");
}
})
doclist.copy = true;
}
},
add_menu_item_paste:function(doclist){
var paste_menu = doclist.page.add_menu_item(__("Paste"), function(){
frappe.call({
method:"frappe.core.doctype.file.file.move_file",
args: {
"file_list": doclist.selected_files,
"new_parent": doclist.current_folder,
"old_parent": doclist.old_parent
},
callback:function(r){
doclist.paste = false;
frappe.msgprint(__(r.message));
doclist.selected_files = [];
$(paste_menu).remove();
}
})
})
},
before_run: function(doclist) {
var name_filter = doclist.filter_list.get_filter("file_name");
if(name_filter) {
doclist.filter_area.removeClass("hide");
doclist.breadcrumb.addClass("hide");
} else {
doclist.filter_area.addClass("hide");
doclist.breadcrumb.removeClass("hide");
}
},
refresh: function(doclist) {
// set folder before querying
var name_filter = doclist.filter_list.get_filter("file_name");

var folder_filter = doclist.filter_list.get_filter("folder");
if(folder_filter) {
folder_filter.remove(true);
}

if(name_filter) return;

var route = frappe.get_route();
if(route[2]) {
doclist.current_folder = route.slice(2).join("/");
doclist.current_folder_name = route.slice(-1)[0];
}

if(!doclist.current_folder) {
doclist.current_folder = frappe.boot.home_folder;
doclist.current_folder_name = __("Home");
}

doclist.filter_list.add_filter("File", "folder", "=", doclist.current_folder, true);
doclist.dirty = true;
doclist.fresh = false;

doclist.page.set_title(doclist.current_folder_name);
frappe.utils.set_title(doclist.current_folder_name);
},
set_primary_action:function(doclist){
doclist.page.clear_primary_action();
doclist.page.set_primary_action(__("New"), function() {
dialog = frappe.ui.get_upload_dialog({
"args": {
"folder": doclist.current_folder,
"from_form": 1
},
callback: function() {
doclist.refresh();
}
});
}, "octicon octicon-plus");
},
post_render_item: function(list, row, data) {
if(data.is_folder) {
$(row).find(".list-id").attr("href", "#List/File/" + data.name);
}
},
set_file_route: function(name) {
frappe.set_route(["List", "File"].concat(decodeURIComponent(name).split("/")));
},
post_render: function(doclist) {
frappe.call({
method: "frappe.core.doctype.file.file.get_breadcrumbs",
args: {
folder: doclist.current_folder
},
callback: function(r) {
doclist.breadcrumb.empty();
if(r.message && r.message.length) {
$.each(r.message, function(i, folder) {
$('<li><a href="#List/File/'+folder.name+'">'
+ folder.file_name+'</a></li>')
.appendTo(doclist.breadcrumb);
});
}
$('<li class="active">'+ doclist.current_folder_name+'</li>')
.appendTo(doclist.breadcrumb);
}
});
}
}

+ 106
- 0
frappe/core/doctype/file/test_file.py Переглянути файл

@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals

import frappe
import unittest
from frappe.utils.file_manager import save_file, get_file, get_files_path
from frappe import _
from frappe.core.doctype.file.file import move_file
import json
# test_records = frappe.get_test_records('File')

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"]:
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",
"file_name": _(folder_name),
"is_folder": 1,
"folder": _(parent_folder)
}).insert()

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")
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")
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.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)

+ 0
- 3
frappe/core/doctype/file_data/README.md Переглянути файл

@@ -1,3 +0,0 @@
File record and its relation to document to which the file is attached.

This is appened to the form as `docinfo` when a document is loaded.

+ 0
- 4
frappe/core/doctype/file_data/__init__.py Переглянути файл

@@ -1,4 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals

+ 0
- 179
frappe/core/doctype/file_data/file_data.json Переглянути файл

@@ -1,179 +0,0 @@
{
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "File.######",
"creation": "2012-12-12 11:19:22",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "file_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "File Name",
"no_copy": 0,
"oldfieldname": "file_name",
"oldfieldtype": "Data",
"permlevel": 0,
"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,
"collapsible": 0,
"fieldname": "file_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "File URL",
"no_copy": 0,
"permlevel": 0,
"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,
"collapsible": 0,
"fieldname": "attached_to_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Attached To DocType",
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "attached_to_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Attached To Name",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "file_size",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "File Size",
"no_copy": 0,
"permlevel": 0,
"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,
"collapsible": 0,
"fieldname": "content_hash",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Content Hash",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-file",
"idx": 1,
"in_create": 1,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-02-05 05:11:38.944926",
"modified_by": "Administrator",
"module": "Core",
"name": "File Data",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0
}

+ 0
- 57
frappe/core/doctype/file_data/file_data.py Переглянути файл

@@ -1,57 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
"""
record of files

naming for same name files: file.gif, file-1.gif, file-2.gif etc
"""

import frappe, frappe.utils, os
from frappe import conf
from frappe.model.document import Document
from frappe.utils.file_manager import delete_file_data_content

class FileData(Document):
no_feed_on_delete = True

def before_insert(self):
frappe.local.rollback_observers.append(self)

def validate(self):
if not self.flags.ignore_duplicate_entry_error:
# check duplicate assignement
n_records = frappe.db.sql("""select name from `tabFile Data`
where content_hash=%s
and name!=%s
and attached_to_doctype=%s
and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype,
self.attached_to_name))
if len(n_records) > 0:
self.duplicate_entry = n_records[0][0]
frappe.throw(frappe._("Same file has already been attached to the record"), frappe.DuplicateEntryError)

def on_trash(self):
if self.attached_to_name:
# check persmission
try:
if not self.flags.ignore_permissions and \
not frappe.has_permission(self.attached_to_doctype, "write", self.attached_to_name):

frappe.msgprint(frappe._("No permission to write / remove."), raise_exception=True)

except frappe.DoesNotExistError:
pass

# if file not attached to any other record, delete it
if self.file_name and self.content_hash and (not frappe.db.count("File Data",
{"content_hash": self.content_hash, "name": ["!=", self.name]})):
delete_file_data_content(self)

def on_rollback(self):
self.on_trash()

def on_doctype_update():
frappe.db.add_index("File Data", ["attached_to_doctype", "attached_to_name"])


+ 7
- 3
frappe/core/doctype/page/page.py Переглянути файл

@@ -85,16 +85,18 @@ class Page(Document):
from frappe.modules import get_module_path, scrub from frappe.modules import get_module_path, scrub
import os import os


path = os.path.join(get_module_path(self.module), 'page', scrub(self.name))
page_name = scrub(self.name)

path = os.path.join(get_module_path(self.module), 'page', page_name)


# script # script
fpath = os.path.join(path, scrub(self.name) + '.js')
fpath = os.path.join(path, page_name + '.js')
if os.path.exists(fpath): if os.path.exists(fpath):
with open(fpath, 'r') as f: with open(fpath, 'r') as f:
self.script = unicode(f.read(), "utf-8") self.script = unicode(f.read(), "utf-8")


# css # css
fpath = os.path.join(path, scrub(self.name) + '.css')
fpath = os.path.join(path, page_name + '.css')
if os.path.exists(fpath): if os.path.exists(fpath):
with open(fpath, 'r') as f: with open(fpath, 'r') as f:
self.style = unicode(f.read(), "utf-8") self.style = unicode(f.read(), "utf-8")
@@ -109,3 +111,5 @@ class Page(Document):
if frappe.lang != 'en': if frappe.lang != 'en':
from frappe.translate import get_lang_js from frappe.translate import get_lang_js
self.script += get_lang_js("page", self.name) self.script += get_lang_js("page", self.name)



+ 3
- 2
frappe/data/Framework.sql Переглянути файл

@@ -200,11 +200,11 @@ CREATE TABLE `__Auth` (
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


-- --
-- Table structure for table `tabFile`
-- --


DROP TABLE IF EXISTS `tabFile Data`;
CREATE TABLE `tabFile Data` (
DROP TABLE IF EXISTS `tabFile`;
CREATE TABLE `tabFile` (
`name` varchar(255) NOT NULL, `name` varchar(255) NOT NULL,
`creation` datetime(6) DEFAULT NULL, `creation` datetime(6) DEFAULT NULL,
`modified` datetime(6) DEFAULT NULL, `modified` datetime(6) DEFAULT NULL,


+ 2
- 2
frappe/desk/form/load.py Переглянути файл

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


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


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


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


+ 11
- 7
frappe/desk/search.py Переглянути файл

@@ -59,14 +59,18 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,


# build from doctype # build from doctype
if txt: if txt:
search_fields = ["name"]
if meta.title_field:
search_fields.append(meta.title_field)

if meta.search_fields: if meta.search_fields:
for f in meta.get_search_fields():
fmeta = meta.get_field(f.strip())
if f == "name" or (fmeta and fmeta.fieldtype in ["Data", "Text", "Small Text", "Long Text",
"Link", "Select", "Read Only", "Text Editor"]):
or_filters.append([doctype, f.strip(), "like", "%{0}%".format(txt)])
else:
filters.append([doctype, searchfield or "name", "like", "%{0}%".format(txt)])
search_fields.extend(meta.get_search_fields())
for f in search_fields:
fmeta = meta.get_field(f.strip())
if f == "name" or (fmeta and fmeta.fieldtype in ["Data", "Text", "Small Text", "Long Text",
"Link", "Select", "Read Only", "Text Editor"]):
or_filters.append([doctype, f.strip(), "like", "%{0}%".format(txt)])


if meta.get("fields", {"fieldname":"enabled", "fieldtype":"Check"}): if meta.get("fields", {"fieldname":"enabled", "fieldtype":"Check"}):
filters.append([doctype, "enabled", "=", 1]) filters.append([doctype, "enabled", "=", 1])


+ 2
- 2
frappe/email/doctype/email_account/test_email_account.py Переглянути файл

@@ -41,8 +41,8 @@ class TestEmailAccount(unittest.TestCase):


def test_incoming_with_attach(self): def test_incoming_with_attach(self):
frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'") frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
existing_file = frappe.get_doc({'doctype': 'File Data', 'file_name': 'erpnext-conf-14.png'})
frappe.delete_doc("File Data", existing_file.name)
existing_file = frappe.get_doc({'doctype': 'File', 'file_name': 'erpnext-conf-14.png'})
frappe.delete_doc("File", existing_file.name)
delete_file_from_filesystem(existing_file) delete_file_from_filesystem(existing_file)


with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-2.raw"), "r") as f: with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-2.raw"), "r") as f:


+ 2
- 2
frappe/frappeclient.py Переглянути файл

@@ -177,8 +177,8 @@ class FrappeClient(object):
self.migrate_doctype("Comment", {"comment_doctype": doctype, "comment_docname": doc["name"]}, self.migrate_doctype("Comment", {"comment_doctype": doctype, "comment_docname": doc["name"]},
update={"comment_docname": new_doc.name}, verbose=0) update={"comment_docname": new_doc.name}, verbose=0)


if doctype != "File Data":
self.migrate_doctype("File Data", {"attached_to_doctype": doctype,
if doctype != "File":
self.migrate_doctype("File", {"attached_to_doctype": doctype,
"attached_to_name": doc["name"]}, update={"attached_to_name": new_doc.name}, verbose=0) "attached_to_name": doc["name"]}, update={"attached_to_name": new_doc.name}, verbose=0)


def migrate_single(self, doctype): def migrate_single(self, doctype):


+ 1
- 0
frappe/model/delete_doc.py Переглянути файл

@@ -82,6 +82,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa


update_naming_series(doc) update_naming_series(doc)
delete_from_table(doctype, name, ignore_doctypes, doc) delete_from_table(doctype, name, ignore_doctypes, doc)
doc.run_method("after_delete")


if doc: if doc:
try: try:


+ 4
- 0
frappe/model/document.py Переглянути файл

@@ -86,6 +86,10 @@ class Document(BaseDocument):
self._default_new_docs = {} self._default_new_docs = {}
self.flags = frappe._dict() self.flags = frappe._dict()


def reload(self):
"""Reload document from database"""
self.load_from_db()

def load_from_db(self): def load_from_db(self):
"""Load document and children from database and create properties """Load document and children from database and create properties
from fields""" from fields"""


+ 4
- 3
frappe/model/rename_doc.py Переглянути файл

@@ -66,8 +66,9 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F


def update_attachments(doctype, old, new): def update_attachments(doctype, old, new):
try: try:
frappe.db.sql("""update `tabFile Data` set attached_to_name=%s
where attached_to_name=%s and attached_to_doctype=%s""", (new, old, doctype))
if old != "File Data" and doctype != "DocType":
frappe.db.sql("""update `tabFile` set attached_to_name=%s
where attached_to_name=%s and attached_to_doctype=%s""", (new, old, doctype))
except Exception, e: except Exception, e:
if e.args[0]!=1054: # in patch? if e.args[0]!=1054: # in patch?
raise raise
@@ -96,7 +97,7 @@ def validate_rename(doctype, new, meta, merge, force, ignore_permissions):
if not (ignore_permissions or frappe.has_permission(doctype, "write")): if not (ignore_permissions or frappe.has_permission(doctype, "write")):
frappe.msgprint(_("You need write permission to rename"), raise_exception=1) frappe.msgprint(_("You need write permission to rename"), raise_exception=1)


if not force and not meta.allow_rename:
if not (force or ignore_permissions) and not meta.allow_rename:
frappe.msgprint(_("{0} not allowed to be renamed").format(_(doctype)), raise_exception=1) frappe.msgprint(_("{0} not allowed to be renamed").format(_(doctype)), raise_exception=1)


# validate naming like it's done in doc.py # validate naming like it's done in doc.py


+ 1
- 0
frappe/patches.txt Переглянути файл

@@ -91,3 +91,4 @@ frappe.patches.v6_0.document_type_rename
frappe.patches.v6_0.fix_ghana_currency frappe.patches.v6_0.fix_ghana_currency
frappe.patches.v6_2.ignore_user_permissions_if_missing frappe.patches.v6_2.ignore_user_permissions_if_missing
execute:frappe.db.sql("delete from tabSessions where user is null") execute:frappe.db.sql("delete from tabSessions where user is null")
frappe.patches.v6_1.rename_file_data

+ 2
- 2
frappe/patches/v4_0/file_manager_hooks.py Переглянути файл

@@ -12,9 +12,9 @@ from frappe.utils.file_manager import get_content_hash, get_file
def execute(): def execute():
frappe.reload_doc('core', 'doctype', 'file_data') frappe.reload_doc('core', 'doctype', 'file_data')
for name, file_name, file_url in frappe.db.sql( for name, file_name, file_url in frappe.db.sql(
"""select name, file_name, file_url from `tabFile Data`
"""select name, file_name, file_url from `tabFile`
where file_name is not null"""): where file_name is not null"""):
b = frappe.get_doc('File Data', name)
b = frappe.get_doc('File', name)
old_file_name = b.file_name old_file_name = b.file_name
b.file_name = os.path.basename(old_file_name) b.file_name = os.path.basename(old_file_name)
if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): if old_file_name.startswith('files/') or old_file_name.startswith('/files/'):


+ 7
- 7
frappe/patches/v4_1/file_manager_fix.py Переглянути файл

@@ -18,15 +18,15 @@ from frappe.utils import get_files_path, get_site_path
# * make missing_files.txt in site dir with files that should be recovered from # * make missing_files.txt in site dir with files that should be recovered from
# a backup from a time before version 3 migration # a backup from a time before version 3 migration
# #
# * Patch remaining unpatched file data records.
# * Patch remaining unpatched File records.


def execute(): def execute():
frappe.db.auto_commit_on_many_writes = True frappe.db.auto_commit_on_many_writes = True
rename_replacing_files() rename_replacing_files()
for name, file_name, file_url in frappe.db.sql( for name, file_name, file_url in frappe.db.sql(
"""select name, file_name, file_url from `tabFile Data`
"""select name, file_name, file_url from `tabFile`
where ifnull(file_name, '')!='' and ifnull(content_hash, '')=''"""): where ifnull(file_name, '')!='' and ifnull(content_hash, '')=''"""):
b = frappe.get_doc('File Data', name)
b = frappe.get_doc('File', name)
old_file_name = b.file_name old_file_name = b.file_name
b.file_name = os.path.basename(old_file_name) b.file_name = os.path.basename(old_file_name)
if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): if old_file_name.startswith('files/') or old_file_name.startswith('/files/'):
@@ -45,8 +45,8 @@ def execute():


def get_replaced_files(): def get_replaced_files():
ret = [] ret = []
new_files = dict(frappe.db.sql("select name, file_name from `tabFile Data` where file_name not like 'files/%'"))
old_files = dict(frappe.db.sql("select name, file_name from `tabFile Data` where ifnull(content_hash, '')=''"))
new_files = dict(frappe.db.sql("select name, file_name from `tabFile` where file_name not like 'files/%'"))
old_files = dict(frappe.db.sql("select name, file_name from `tabFile` where ifnull(content_hash, '')=''"))
invfiles = invert_dict(new_files) invfiles = invert_dict(new_files)


for nname, nfilename in new_files.iteritems(): for nname, nfilename in new_files.iteritems():
@@ -63,7 +63,7 @@ def rename_replacing_files():


for file_name, file_datas in replaced_files: for file_name, file_datas in replaced_files:
print 'processing ' + file_name print 'processing ' + file_name
content_hash = frappe.db.get_value('File Data', file_datas[0], 'content_hash')
content_hash = frappe.db.get_value('File', file_datas[0], 'content_hash')
if not content_hash: if not content_hash:
continue continue
new_file_name = get_file_name(file_name, content_hash) new_file_name = get_file_name(file_name, content_hash)
@@ -75,7 +75,7 @@ def rename_replacing_files():
except OSError: except OSError:
print 'Error renaming ', file_name print 'Error renaming ', file_name
for name in file_datas: for name in file_datas:
f = frappe.get_doc('File Data', name)
f = frappe.get_doc('File', name)
f.file_name = new_file_name f.file_name = new_file_name
f.file_url = '/files/' + new_file_name f.file_url = '/files/' + new_file_name
f.save() f.save()


+ 0
- 0
frappe/patches/v6_1/__init__.py Переглянути файл


+ 30
- 0
frappe/patches/v6_1/rename_file_data.py Переглянути файл

@@ -0,0 +1,30 @@
import frappe

def execute():
from frappe.core.doctype.file.file import make_home_folder

if not frappe.db.exists("DocType", "File"):
frappe.rename_doc("DocType", "File Data", "File")
frappe.reload_doctype("File")

if not frappe.db.exists("File", {"is_home_folder": 1}):
make_home_folder()

# make missing folders and set parent folder
for file in frappe.get_all("File", filters={"is_folder": 0}):
file = frappe.get_doc("File", file.name)
file.flags.ignore_folder_validate = True
file.set_folder_name()
file.save()

from frappe.utils.nestedset import rebuild_tree
rebuild_tree("File", "folder")

# reset file size
for folder in frappe.db.sql("""select name from tabFile f1 where is_folder = 1 and
(select count(*) from tabFile f2 where f2.folder = f1.name and f2.is_folder = 1) = 0"""):
folder = frappe.get_doc("File", folder[0])
folder.save()




+ 37
- 17
frappe/permissions.py Переглянути файл

@@ -52,6 +52,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
if doc_name in shared: if doc_name in shared:
if verbose: print "Shared" if verbose: print "Shared"
if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype): if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype):
if verbose: print "Is shared"
return True return True


elif shared: elif shared:
@@ -60,6 +61,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
if verbose: print "Has a shared document" if verbose: print "Has a shared document"
return True return True


if verbose: print "Not Shared"
return False return False


role_permissions = get_role_permissions(meta, user=user, verbose=verbose) role_permissions = get_role_permissions(meta, user=user, verbose=verbose)
@@ -67,28 +69,40 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
if not role_permissions.get(ptype): if not role_permissions.get(ptype):
return false_if_not_shared() return false_if_not_shared()


perm = True

if doc: if doc:
if isinstance(doc, basestring): if isinstance(doc, basestring):
doc = frappe.get_doc(meta.name, doc) doc = frappe.get_doc(meta.name, doc)


# if owner match, then return True
if doc.owner == frappe.session.user and role_permissions["if_owner"].get(ptype) and ptype!="create":
return True
owner_perm = user_perm = controller_perm = None

if role_permissions["if_owner"].get(ptype) and ptype!="create":
owner_perm = doc.owner == frappe.session.user
if verbose: print "Owner permission: {0}".format(owner_perm)


# check if user permission # check if user permission
if role_permissions["apply_user_permissions"].get(ptype):
if not user_has_permission(doc, verbose=verbose, user=user,
user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or []):
if verbose: print "No user permission"
return false_if_not_shared()

if not has_controller_permissions(doc, ptype, user=user):
if verbose: print "No controller permission"
return false_if_not_shared()

if verbose:
print "Has Role"
return True
if not owner_perm and role_permissions["apply_user_permissions"].get(ptype):
user_perm = user_has_permission(doc, verbose=verbose, user=user,
user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or [])

if verbose: print "User permission: {0}".format(user_perm)

if not owner_perm and not user_perm:
controller_perm = has_controller_permissions(doc, ptype, user=user)

if verbose: print "Controller permission: {0}".format(controller_perm)

# permission true if any one condition is explicitly True or all permissions are undefined (None)
perm = any([owner_perm, user_perm, controller_perm]) or \
all([owner_perm==None, user_perm==None, controller_perm==None])

if not perm:
perm = false_if_not_shared()

if verbose: print "Final Permission: {0}".format(perm)

return perm


def get_doc_permissions(doc, verbose=False, user=None): def get_doc_permissions(doc, verbose=False, user=None):
"""Returns a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`""" """Returns a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`"""
@@ -271,9 +285,15 @@ def user_has_permission(doc, verbose=True, user=None, user_permission_doctypes=N
return _user_has_permission return _user_has_permission


def has_controller_permissions(doc, ptype, user=None): def has_controller_permissions(doc, ptype, user=None):
"""Returns controller permissions if defined. None if not defined"""
if not user: user = frappe.session.user if not user: user = frappe.session.user


for method in frappe.get_hooks("has_permission").get(doc.doctype, []):
methods = frappe.get_hooks("has_permission").get(doc.doctype, [])

if not methods:
return None

for method in methods:
if not frappe.call(frappe.get_attr(method), doc=doc, ptype=ptype, user=user): if not frappe.call(frappe.get_attr(method), doc=doc, ptype=ptype, user=user):
return False return False




+ 9
- 0
frappe/public/css/desk.css Переглянути файл

@@ -521,3 +521,12 @@ ul.linked-with-list li {
height: 451px; height: 451px;
font-family: Monaco, "Courier New", monospace; font-family: Monaco, "Courier New", monospace;
} }
.breadcrumb {
font-size: 12px;
background-color: #fff;
}
.breadcrumb.for-file-list {
margin-bottom: 0px;
border-bottom: 1px solid #d1d8dd;
border-radius: 0px;
}

+ 41
- 20
frappe/public/js/frappe/form/footer/attachments.js Переглянути файл

@@ -145,28 +145,14 @@ frappe.ui.form.Attachments = Class.extend({
}, },
new_attachment: function(fieldname) { new_attachment: function(fieldname) {
var me = this; var me = this;
if(!this.dialog) {
this.dialog = new frappe.ui.Dialog({
title: __('Upload Attachment'),
if(!this.dialog){
this.dialog = frappe.ui.get_upload_dialog({
"args": me.get_args(),
"callback": function(attachment, r) { me.attachment_uploaded(attachment, r) },
"max_width": me.frm.cscript ? me.frm.cscript.attachment_max_width : null,
"max_height": me.frm.cscript ? me.frm.cscript.attachment_max_height : null
}); });
} }
this.dialog.show();
this.fieldname = fieldname;

$(this.dialog.body).empty();
frappe.upload.make({
parent: this.dialog.body,
args: this.get_args(),
callback: function(attachment, r) {
me.attachment_uploaded(attachment, r);
},
onerror: function() {
me.dialog.hide();
},
btn: this.dialog.set_primary_action(__("Attach")),
max_width: this.frm.cscript ? this.frm.cscript.attachment_max_width : null,
max_height: this.frm.cscript ? this.frm.cscript.attachment_max_height : null,
});
}, },
get_args: function() { get_args: function() {
return { return {
@@ -213,3 +199,38 @@ frappe.ui.form.Attachments = Class.extend({
this.refresh(); this.refresh();
} }
}); });

frappe.ui.get_upload_dialog = function(opts){
dialog = new frappe.ui.Dialog({
title: __('Upload Attachment'),
});

var btn = dialog.set_primary_action(__("Attach"));
btn.removeClass("btn-primary").addClass("btn-default");

dialog.show();

$(dialog.body).empty();

frappe.upload.make({
parent: dialog.body,
args: opts.args,
callback: function(attachment, r) {
dialog.hide();
if(opts.callback){
opts.callback(attachment, r);
}
},
on_select: function() {
btn.removeClass("btn-default").addClass("btn-primary");
},
onerror: function() {
dialog.hide();
},
btn: btn,
max_width: opts.max_width,
max_height: opts.max_height,
});

return dialog;
}

+ 76
- 27
frappe/public/js/frappe/list/doclistview.js Переглянути файл

@@ -14,10 +14,14 @@ frappe.views.ListFactory = frappe.views.Factory.extend({
if(locals["DocType"][doctype].issingle) { if(locals["DocType"][doctype].issingle) {
frappe.set_re_route("Form", doctype); frappe.set_re_route("Form", doctype);
} else { } else {
new frappe.views.DocListView({
doctype: doctype,
parent: me.make_page(true)
});
if(!frappe.views.doclistview[doctype]) {
frappe.views.doclistview[doctype] = new frappe.views.DocListView({
doctype: doctype,
parent: me.make_page(true, "List/" + doctype)
});
} else {
frappe.container.change_to(frappe.views.doclistview[doctype].page_name);
}
me.set_cur_list(); me.set_cur_list();
} }
}); });
@@ -44,13 +48,18 @@ $(document).on("save", function(event, doc) {
frappe.views.set_list_as_dirty = function(doctype) { frappe.views.set_list_as_dirty = function(doctype) {
var list_page = "List/" + doctype; var list_page = "List/" + doctype;
if(frappe.pages[list_page]) { if(frappe.pages[list_page]) {
if(frappe.pages[list_page].doclistview)
if(frappe.pages[list_page].doclistview) {
if(frappe.pages[list_page].doclistview.dirty) {
// already refreshing...
return;
}
frappe.pages[list_page].doclistview.dirty = true; frappe.pages[list_page].doclistview.dirty = true;
}
} }
var route = frappe.get_route(); var route = frappe.get_route();
if(route[0]==="List" && route[1]===doctype) { if(route[0]==="List" && route[1]===doctype) {
setTimeout(function() { setTimeout(function() {
frappe.pages[list_page].doclistview.run();
frappe.pages[list_page].doclistview.refresh();
}, 100); }, 100);
} }
} }
@@ -65,6 +74,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
}; };


this.label = __(this.doctype); this.label = __(this.doctype);
this.page_name = "List/" + this.doctype;
this.dirty = true; this.dirty = true;
this.tags_shown = false; this.tags_shown = false;
this.label = (this.label.toLowerCase().substr(-4) == 'list') ? this.label = (this.label.toLowerCase().substr(-4) == 'list') ?
@@ -118,7 +128,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
&& !this.listview.no_delete) && !this.listview.no_delete)
}); });


this.list_header = $(frappe.render_template("list_item_row_head", { main:main, list:this }))
this.list_header = $(frappe.render_template("list_item_row_head", { main:main, list:this.listview }))
.appendTo(this.page.main.find(".list-headers")); .appendTo(this.page.main.find(".list-headers"));
}, },


@@ -153,7 +163,9 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
added = true; added = true;
} }
}); });
added && me.run();
if(added) {
me.refresh(true);
}
}); });
this.$page.find(".result-list").on("click", ".list-row-left", function(e) { this.$page.find(".result-list").on("click", ".list-row-left", function(e) {
// don't open in case of checkbox, star, filterable // don't open in case of checkbox, star, filterable
@@ -218,16 +230,21 @@ frappe.views.DocListView = frappe.ui.Listing.extend({


// make_new_doc can be overridden so that default values can be prefilled // make_new_doc can be overridden so that default values can be prefilled
// for example - communication list in customer // for example - communication list in customer
$(this.wrapper).on("click", 'button[list_view_doc="'+me.doctype+'"]', function(){
(me.listview.make_new_doc || me.make_new_doc).apply(me, [me.doctype]);
});
if(this.listview.settings.list_view_doc) {
this.listview.settings.list_view_doc(this);
}
else{
$(this.wrapper).on("click", 'button[list_view_doc="'+me.doctype+'"]', function(){
(me.listview.make_new_doc || me.make_new_doc).apply(me, [me.doctype]);
});
}


if((auto_run !== false) && (auto_run !== 0)) if((auto_run !== false) && (auto_run !== 0))
this.refresh(); this.refresh();
}, },


refresh: function() {
var me = this;
refresh: function(dirty) {
if(dirty!==undefined) this.dirty = dirty;
this.init_stats(); this.init_stats();


if(this.listview.settings.refresh) { if(this.listview.settings.refresh) {
@@ -235,13 +252,14 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
} }


if(frappe.route_options) { if(frappe.route_options) {
me.set_route_options();
} else if(me.dirty) {
me.run();
this.set_route_options();
this.run();
} else if(this.dirty) {
this.run();
} else { } else {
if(new Date() - (me.last_updated_on || 0) > 30000) {
if(new Date() - (this.last_updated_on || 0) > 30000) {
// older than 5 mins, refresh // older than 5 mins, refresh
me.run();
this.run();
} }
} }
}, },
@@ -265,17 +283,28 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
} }
}); });
frappe.route_options = null; frappe.route_options = null;
me.run();
}, },


run: function(more) { run: function(more) {
// set filter from route // set filter from route
var route = frappe.get_route();
var me = this; var me = this;
if(route[2]) {
$.each(frappe.utils.get_args_dict_from_url(route[2]), function(key, val) {
me.set_filter(key, val, true);
});

if(this.fresh && !more) {
return;
}

if(this.listview.settings.before_run) {
this.listview.settings.before_run(this);
}

if(!this.listview.settings.use_route) {
var route = frappe.get_route();
var me = this;
if(route[2]) {
$.each(frappe.utils.get_args_dict_from_url(route[2]), function(key, val) {
me.set_filter(key, val, true);
});
}
} }


this.list_header.find(".list-starred-by-me") this.list_header.find(".list-starred-by-me")
@@ -283,7 +312,25 @@ frappe.views.DocListView = frappe.ui.Listing.extend({


this.last_updated_on = new Date(); this.last_updated_on = new Date();
this.dirty = false; this.dirty = false;

// set a fresh so that multiple refreshes do not happen
// at the same time. This is true when deleting.
// AJAX response will try to refresh and list_update notification
// via async will also try to update.
// It is not possible to guess which will reach first
// (most probably async will) but this is a forced way
// to prevent instant refreshes on mutilple triggers
// in a loosly coupled way.
this.fresh = true;
setTimeout(function() {
me.fresh = false;
}, 1000);

this._super(more); this._super(more);

if(this.listview.settings.post_render) {
this.listview.settings.post_render(this);
}
}, },


make_no_result: function() { make_no_result: function() {
@@ -336,12 +383,14 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
this.$page.on("click", ".list-tag-preview", function() { me.toggle_tags(); }); this.$page.on("click", ".list-tag-preview", function() { me.toggle_tags(); });


this.page.set_secondary_action(__("Refresh"), function() { this.page.set_secondary_action(__("Refresh"), function() {
me.run();
me.dirty = true;
me.refresh();
}, "octicon octicon-sync"); }, "octicon octicon-sync");


this.page.btn_secondary.addClass("hidden-xs"); this.page.btn_secondary.addClass("hidden-xs");
this.page.add_menu_item(__("Refresh"), function() { this.page.add_menu_item(__("Refresh"), function() {
me.run();
me.dirty = true;
me.refresh();
}, "octicon octicon-sync").addClass("visible-xs"); }, "octicon octicon-sync").addClass("visible-xs");


if(frappe.model.can_import(this.doctype)) { if(frappe.model.can_import(this.doctype)) {
@@ -526,7 +575,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
callback: function(r) { callback: function(r) {
if(!r.exc) { if(!r.exc) {
me.list_header.find(".list-select-all").prop("checked", false); me.list_header.find(".list-select-all").prop("checked", false);
me.run();
me.refresh();
} }
} }
}); });


+ 7
- 2
frappe/public/js/frappe/list/list_item_main.html Переглянути файл

@@ -20,7 +20,7 @@
{% if (col.type==="Subject") { %} {% if (col.type==="Subject") { %}
{%= subject %} {%= subject %}
{% } else if (col.type==="Indicator") { %} {% } else if (col.type==="Indicator") { %}
{%= me.get_indicator(data) %}
{%= list.get_indicator(data) %}
{% } else if (col.render) { %} {% } else if (col.render) { %}
{%= col.render(data) %} {%= col.render(data) %}
{% } else if (col.fieldtype==="Image") { %} {% } else if (col.fieldtype==="Image") { %}
@@ -36,7 +36,12 @@
<a class="filterable h6 text-muted grey" <a class="filterable h6 text-muted grey"
data-filter="{%= col.fieldname %},=,{%= value %}">{%= value %}</a> data-filter="{%= col.fieldname %},=,{%= value %}">{%= value %}</a>
{% } else { %} {% } else { %}
{%= frappe.format(value, col.df, null, data) %}
{% if(list.settings.formatters
&& list.settings.formatters[col.fieldname]) { %}
{{ list.settings.formatters[col.fieldname](value, col.df, data) }}
{% } else { %}
{{ frappe.format(value, col.df, null, data) }}
{% } %}
{% } %} {% } %}
{% if(col.type!=="Indicator") { %}</span>{% } %} {% if(col.type!=="Indicator") { %}</span>{% } %}
</div> </div>


+ 2
- 2
frappe/public/js/frappe/list/list_item_row.html Переглянути файл

@@ -1,6 +1,6 @@
<div class="row doclist-row {% if (data._checkbox) { %} has-checkbox {% } %}"> <div class="row doclist-row {% if (data._checkbox) { %} has-checkbox {% } %}">
<div class="col-xs-10 <div class="col-xs-10
{% if (list.meta.title_field) { %}
{% if (list.meta.title_field && !list.settings.hide_name_column) { %}
col-sm-8 col-sm-8
{% } else { %} {% } else { %}
col-sm-10 col-sm-10
@@ -10,7 +10,7 @@
</div> </div>


<!-- id --> <!-- id -->
{% if (list.meta.title_field) {
{% if (list.meta.title_field && !list.settings.hide_name_column) {
var is_different = data.name !== data[list.meta.title_field]; var is_different = data.name !== data[list.meta.title_field];
%} %}
<div class="list-col col-sm-2 hidden-xs text-right text-ellipsis rtl list-row-id"> <div class="list-col col-sm-2 hidden-xs text-right text-ellipsis rtl list-row-id">


+ 2
- 2
frappe/public/js/frappe/list/list_item_row_head.html Переглянути файл

@@ -1,7 +1,7 @@
<div class="list-row list-row-head"> <div class="list-row list-row-head">
<div class="row doclist-row"> <div class="row doclist-row">
<div class="col-xs-10 <div class="col-xs-10
{% if (list.meta.title_field) { %}
{% if (list.meta.title_field && !list.settings.hide_name_column) { %}
col-sm-8 col-sm-8
{% } else { %} {% } else { %}
col-sm-10 col-sm-10
@@ -11,7 +11,7 @@
</div> </div>


<!-- id --> <!-- id -->
{% if (list.meta.title_field) { %}
{% if (list.meta.title_field && !list.settings.hide_name_column) { %}
<div class="list-col col-sm-2 hidden-xs text-right"> <div class="list-col col-sm-2 hidden-xs text-right">
</div> </div>
{% } %} {% } %}


+ 14
- 8
frappe/public/js/frappe/list/list_item_subject.html Переглянути файл

@@ -1,14 +1,20 @@
{% if (_checkbox) { %} {% if (_checkbox) { %}
<input class="list-delete" type="checkbox" style="margin: 0 7px 0 0; vertical-align: middle;">
<input class="list-delete" type="checkbox"
style="margin: 0 7px 0 0; vertical-align: middle;">
{% } %} {% } %}
<i class="icon-star {% if (_starred_by.indexOf(_user)===-1) {
%}text-extra-muted not-starred{% } else { %}{% }%}
icon-fixed-width star-action" data-name="{%= _name %}" data-doctype="{%= doctype %}">
<i class="icon-star
{% if (_starred_by.indexOf(_user)===-1) { %}
text-extra-muted not-starred
{% }%}
icon-fixed-width star-action"
data-name="{{ _name }}" data-doctype="{{ doctype }}">
</i> </i>
<a class="grey list-id" style="margin-right: 7px;"
href="#Form/{%= _doctype_encoded %}/{%= _name_encoded %}" title="{%= _full_title %}">{%= _title %}</a>
<a class="grey list-id"
style="margin-right: 7px;"
href="#Form/{{ _doctype_encoded }}/{{ _name_encoded }}"
title="{{ _full_title }}">{{ _title }}</a>
{% if (_workflow && !_without_workflow) { %} {% if (_workflow && !_without_workflow) { %}
<span class="label label-{%= _workflow.style %} filterable"
data-filter="{%= _workflow.fieldname %},=,{%= _workflow.value %}">
<span class="label label-{{ _workflow.style }} filterable"
data-filter="{{ _workflow.fieldname }},=,{{ _workflow.value }}">
{%= _workflow.value %}</span> {%= _workflow.value %}</span>
{% } %} {% } %}

+ 5
- 1
frappe/public/js/frappe/list/listview.js Переглянути файл

@@ -208,7 +208,7 @@ frappe.views.ListView = Class.extend({
data: data, data: data,
columns: this.columns, columns: this.columns,
subject: this.get_avatar_and_id(data, true), subject: this.get_avatar_and_id(data, true),
me: this,
list: this,
right_column: this.settings.right_column right_column: this.settings.right_column
}); });


@@ -219,6 +219,10 @@ frappe.views.ListView = Class.extend({
right_column: this.settings.right_column right_column: this.settings.right_column
})).appendTo(row); })).appendTo(row);


if(this.settings.post_render_item) {
this.settings.post_render_item(this, row, data);
}

this.render_tags(row, data); this.render_tags(row, data);


}, },


+ 4
- 0
frappe/public/js/frappe/misc/utils.js Переглянути файл

@@ -489,5 +489,9 @@ frappe.utils = {


// reset the original title // reset the original title
frappe.utils.set_title(frappe._original_title); frappe.utils.set_title(frappe._original_title);
},

is_image_file: function(filename) {
return (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(filename);
} }
}; };

+ 15
- 4
frappe/public/js/frappe/model/perm.js Переглянути файл

@@ -60,6 +60,20 @@ $.extend(frappe.perm, {
}); });
} }


// if owner
if(!$.isEmptyObject(perm[0].if_owner)) {
if(doc.owner===user) {
$.extend(perm[0], perm[0].if_owner);
} else {
// not owner, remove permissions
$.each(perm[0].if_owner, function(ptype, value) {
if(perm[0].if_owner[ptype]) {
perm[0][ptype] = 0
}
})
}
}

// apply permissions from shared // apply permissions from shared
if(docinfo.shared) { if(docinfo.shared) {
for(var i=0; i<docinfo.shared; i++) { for(var i=0; i<docinfo.shared; i++) {
@@ -78,6 +92,7 @@ $.extend(frappe.perm, {
} }
} }
} }

} }


if(frappe.model.can_read(doctype) && !perm[0].read) { if(frappe.model.can_read(doctype) && !perm[0].read) {
@@ -131,10 +146,6 @@ $.extend(frappe.perm, {
}); });
} }
} }

if (permlevel===0 && p["if_owner"]) {
perm[0]["if_owner"] = 1;
}
} }
}); });




+ 19
- 14
frappe/public/js/frappe/ui/filters/filters.js Переглянути файл

@@ -29,7 +29,7 @@ frappe.ui.FilterList = Class.extend({
this.filters = []; this.filters = [];
}, },


add_filter: function(tablename, fieldname, condition, value) {
add_filter: function(doctype, fieldname, condition, value, hidden) {
this.$w.find('.show_filters').toggle(true); this.$w.find('.show_filters').toggle(true);
var is_new_filter = arguments.length===0; var is_new_filter = arguments.length===0;


@@ -38,21 +38,26 @@ frappe.ui.FilterList = Class.extend({
return; return;
} }


var filter = this.push_new_filter(tablename, fieldname, condition, value);
var filter = this.push_new_filter(doctype, fieldname, condition, value);


if (is_new_filter) {
if (filter && is_new_filter) {
filter.$w.addClass("is-new-filter"); filter.$w.addClass("is-new-filter");
} }


if (filter && hidden) {
filter.freeze();
filter.$btn_group.addClass("hide");
}

return filter; return filter;
}, },


push_new_filter: function(tablename, fieldname, condition, value) {
if(this.filter_exists(tablename, fieldname, condition, value)) return;
push_new_filter: function(doctype, fieldname, condition, value) {
if(this.filter_exists(doctype, fieldname, condition, value)) return;


var filter = new frappe.ui.Filter({ var filter = new frappe.ui.Filter({
flist: this, flist: this,
tablename: tablename,
doctype: doctype,
fieldname: fieldname, fieldname: fieldname,
condition: condition, condition: condition,
value: value, value: value,
@@ -63,11 +68,11 @@ frappe.ui.FilterList = Class.extend({
return filter; return filter;
}, },


filter_exists: function(tablename, fieldname, condition, value) {
filter_exists: function(doctype, fieldname, condition, value) {
for(var i in this.filters) { for(var i in this.filters) {
if(this.filters[i].field) { if(this.filters[i].field) {
var f = this.filters[i].get_value(); var f = this.filters[i].get_value();
if(f[0]==tablename && f[1]==fieldname && f[2]==condition
if(f[0]==doctype && f[1]==fieldname && f[2]==condition
&& f[3]==value) return true; && f[3]==value) return true;


} }
@@ -127,7 +132,7 @@ frappe.ui.Filter = Class.extend({
} }
}); });
if(this.fieldname) { if(this.fieldname) {
this.fieldselect.set_value(this.tablename, this.fieldname);
this.fieldselect.set_value(this.doctype, this.fieldname);
} }
}, },
set_events: function() { set_events: function() {
@@ -163,7 +168,7 @@ frappe.ui.Filter = Class.extend({
// set the field // set the field
if(me.fieldname) { if(me.fieldname) {
// pre-sets given (could be via tags!) // pre-sets given (could be via tags!)
this.set_values(me.tablename, me.fieldname, me.condition, me.value);
this.set_values(me.doctype, me.fieldname, me.condition, me.value);
} else { } else {
me.set_field(me.doctype, 'name'); me.set_field(me.doctype, 'name');
} }
@@ -173,17 +178,17 @@ frappe.ui.Filter = Class.extend({
this.$w.remove(); this.$w.remove();
this.$btn_group && this.$btn_group.remove(); this.$btn_group && this.$btn_group.remove();
this.field = null; this.field = null;
this.flist.update_filters();


if(!dont_run) { if(!dont_run) {
this.flist.update_filters();
this.flist.listobj.dirty = true; this.flist.listobj.dirty = true;
this.flist.listobj.run();
this.flist.listobj.refresh();
} }
}, },


set_values: function(tablename, fieldname, condition, value) {
set_values: function(doctype, fieldname, condition, value) {
// presents given (could be via tags!) // presents given (could be via tags!)
this.set_field(tablename, fieldname);
this.set_field(doctype, fieldname);
if(condition) this.$w.find('.condition').val(condition).change(); if(condition) this.$w.find('.condition').val(condition).change();
if(value!=null) this.field.set_input(value); if(value!=null) this.field.set_input(value);
}, },


+ 6
- 2
frappe/public/js/frappe/ui/listing.js Переглянути файл

@@ -118,8 +118,12 @@ frappe.ui.Listing = Class.extend({
set_primary_action: function() { set_primary_action: function() {
var me = this; var me = this;
if(this.new_doctype) { if(this.new_doctype) {
this.page.set_primary_action(__("New"), function() {
me.make_new_doc(); }, "octicon octicon-plus");
if(this.listview.settings.set_primary_action){
this.listview.settings.set_primary_action(this);
} else {
this.page.set_primary_action(__("New"), function() {
me.make_new_doc(me.new_doctype); }, "octicon octicon-plus");
}
} else { } else {
this.page.clear_primary_action(); this.page.clear_primary_action();
} }


+ 6
- 2
frappe/public/js/frappe/ui/page.js Переглянути файл

@@ -234,7 +234,7 @@ frappe.ui.Page = Class.extend({


//-- Sidebar --// //-- Sidebar --//


add_sidebar_item: function(label, action, insert_after) {
add_sidebar_item: function(label, action, insert_after, prepend) {
var parent = this.sidebar.find(".sidebar-menu.standard-actions"); var parent = this.sidebar.find(".sidebar-menu.standard-actions");
var li = $('<li>'); var li = $('<li>');
var link = $('<a>').html(label).on("click", action).appendTo(li); var link = $('<a>').html(label).on("click", action).appendTo(li);
@@ -242,7 +242,11 @@ frappe.ui.Page = Class.extend({
if(insert_after) { if(insert_after) {
li.insertAfter(parent.find(insert_after)); li.insertAfter(parent.find(insert_after));
} else { } else {
li.appendTo(parent);
if(prepend) {
li.prependTo(parent);
} else {
li.appendTo(parent);
}
} }
return link; return link;
}, },


+ 1
- 1
frappe/public/js/frappe/ui/toolbar/awesome_bar.js Переглянути файл

@@ -145,7 +145,7 @@ frappe.search.verbs = [
value: __('Find {0} in {1}', [txt, route[1]]), value: __('Find {0} in {1}', [txt, route[1]]),
route_options: options, route_options: options,
onclick: function() { onclick: function() {
frappe.container.page.doclistview.set_route_options();
cur_list.refresh();
}, },
match: txt match: txt
}); });


+ 6
- 2
frappe/public/js/frappe/upload.js Переглянути файл

@@ -34,6 +34,10 @@ frappe.upload = {
$file_input.trigger("change"); $file_input.trigger("change");
}); });


if(opts.on_select) {
opts.on_select();
}

} else { } else {
$upload.find(".uploaded-filename").addClass("hidden") $upload.find(".uploaded-filename").addClass("hidden")
$upload.find(".web-link-wrapper").removeClass("hidden"); $upload.find(".web-link-wrapper").removeClass("hidden");
@@ -119,13 +123,13 @@ frappe.upload = {
freader.onload = function() { freader.onload = function() {
args.filename = fileobj.name; args.filename = fileobj.name;
if(opts.options && opts.options.toLowerCase()=="image") { if(opts.options && opts.options.toLowerCase()=="image") {
if(!(/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(args.filename)) {
if(!frappe.utils.is_image_file(args.filename)) {
msgprint(__("Only image extensions (.gif, .jpg, .jpeg, .tiff, .png, .svg) allowed")); msgprint(__("Only image extensions (.gif, .jpg, .jpeg, .tiff, .png, .svg) allowed"));
return; return;
} }
} }


if((opts.max_width || opts.max_height) && (/\.(gif|jpg|jpeg|tiff|png)$/i).test(args.filename)) {
if((opts.max_width || opts.max_height) && frappe.utils.is_image_file(args.filename)) {
frappe.utils.resize_image(freader, function(_dataurl) { frappe.utils.resize_image(freader, function(_dataurl) {
dataurl = _dataurl; dataurl = _dataurl;
args.filedata = _dataurl.split(",")[1]; args.filedata = _dataurl.split(",")[1];


+ 7
- 4
frappe/public/js/frappe/views/breadcrumbs.js Переглянути файл

@@ -4,7 +4,9 @@
frappe.breadcrumbs = { frappe.breadcrumbs = {
all: {}, all: {},


preferred: {},
preferred: {
"File": ""
},


set_doctype_module: function(doctype, module) { set_doctype_module: function(doctype, module) {
localStorage["preferred_breadcrumbs:" + doctype] = module; localStorage["preferred_breadcrumbs:" + doctype] = module;
@@ -33,7 +35,7 @@ frappe.breadcrumbs = {


if(from_module) { if(from_module) {
breadcrumbs.module = from_module; breadcrumbs.module = from_module;
} else if(frappe.breadcrumbs.preferred[breadcrumbs.doctype]) {
} else if(frappe.breadcrumbs.preferred[breadcrumbs.doctype]!==undefined) {
// get preferred module for breadcrumbs // get preferred module for breadcrumbs
breadcrumbs.module = frappe.breadcrumbs.preferred[breadcrumbs.doctype]; breadcrumbs.module = frappe.breadcrumbs.preferred[breadcrumbs.doctype];
} }
@@ -60,8 +62,9 @@ frappe.breadcrumbs = {
if(breadcrumbs.doctype==="User" && frappe.user.modules.indexOf("Setup")===-1) { if(breadcrumbs.doctype==="User" && frappe.user.modules.indexOf("Setup")===-1) {
// no user listview for non-system managers // no user listview for non-system managers
} else { } else {
$(repl('<li><a href="#List/%(doctype)s">%(label)s</a></li>',
{doctype: breadcrumbs.doctype, label: __(breadcrumbs.doctype)}))
route = (cur_frm && cur_frm.list_route) || ("List/" + breadcrumbs.doctype)
$(repl('<li><a href="#%(route)s">%(label)s</a></li>',
{route: route, label: __(breadcrumbs.doctype)}))
.appendTo($breadcrumbs); .appendTo($breadcrumbs);
} }
} }


+ 10
- 6
frappe/public/js/frappe/views/factory.js Переглянути файл

@@ -13,7 +13,9 @@ frappe.views.Factory = Class.extend({
me = this; me = this;
if(frappe.pages[page_name] && page_name.indexOf("Form/")===-1) { if(frappe.pages[page_name] && page_name.indexOf("Form/")===-1) {
frappe.container.change_to(frappe.pages[page_name]); frappe.container.change_to(frappe.pages[page_name]);
if(me.on_show) me.on_show();
if(me.on_show) {
me.on_show();
}
} else { } else {
var route = frappe.get_route(); var route = frappe.get_route();
if(route[1]) { if(route[1]) {
@@ -23,14 +25,16 @@ frappe.views.Factory = Class.extend({
} }
} }
}, },
make_page: function(double_column) {
return frappe.make_page(double_column);
make_page: function(double_column, page_name) {
return frappe.make_page(double_column, page_name);
} }
}); });


frappe.make_page = function(double_column) {
var page_name = frappe.get_route_str(),
page = frappe.container.add_page(page_name);
frappe.make_page = function(double_column, page_name) {
if(!page_name) {
var page_name = frappe.get_route_str();
}
var page = frappe.container.add_page(page_name);


frappe.ui.make_app_page({ frappe.ui.make_app_page({
parent: page, parent: page,


+ 11
- 0
frappe/public/less/desk.less Переглянути файл

@@ -377,3 +377,14 @@ ul.linked-with-list li {
height: 451px; height: 451px;
font-family: Monaco, "Courier New", monospace; font-family: Monaco, "Courier New", monospace;
} }

.breadcrumb {
font-size: 12px;
background-color: #fff;
}

.breadcrumb.for-file-list {
margin-bottom: 0px;
border-bottom: 1px solid @border-color;
border-radius: 0px;
}

+ 1
- 1
frappe/utils/csvutils.py Переглянути файл

@@ -18,7 +18,7 @@ def read_csv_content_from_uploaded_file(ignore_encoding=False):
return read_csv_content(fcontent, ignore_encoding) return read_csv_content(fcontent, ignore_encoding)


def read_csv_content_from_attached_file(doc): def read_csv_content_from_attached_file(doc):
fileid = frappe.db.get_value("File Data", {"attached_to_doctype": doc.doctype,
fileid = frappe.db.get_value("File", {"attached_to_doctype": doc.doctype,
"attached_to_name":doc.name}, "name") "attached_to_name":doc.name}, "name")


if not fileid: if not fileid:


+ 28
- 26
frappe/utils/file_manager.py Переглянути файл

@@ -14,13 +14,14 @@ from copy import copy
class MaxFileSizeReachedError(frappe.ValidationError): pass class MaxFileSizeReachedError(frappe.ValidationError): pass


def get_file_url(file_data_name): def get_file_url(file_data_name):
data = frappe.db.get_value("File Data", file_data_name, ["file_name", "file_url"], as_dict=True)
data = frappe.db.get_value("File", file_data_name, ["file_name", "file_url"], as_dict=True)
return data.file_url or data.file_name return data.file_url or data.file_name


def upload(): def upload():
# get record details # get record details
dt = frappe.form_dict.doctype dt = frappe.form_dict.doctype
dn = frappe.form_dict.docname dn = frappe.form_dict.docname
folder = frappe.form_dict.folder
file_url = frappe.form_dict.file_url file_url = frappe.form_dict.file_url
filename = frappe.form_dict.filename filename = frappe.form_dict.filename


@@ -30,11 +31,11 @@ def upload():


# save # save
if filename: if filename:
filedata = save_uploaded(dt, dn)
filedata = save_uploaded(dt, dn, folder)
elif file_url: elif file_url:
filedata = save_url(file_url, dt, dn)

filedata = save_url(file_url, dt, dn, folder)


comment = {}
if dt and dn: if dt and dn:
comment = frappe.get_doc(dt, dn).add_comment("Attachment", 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>".format(**filedata.as_dict())))
@@ -43,32 +44,33 @@ def upload():
"name": filedata.name, "name": filedata.name,
"file_name": filedata.file_name, "file_name": filedata.file_name,
"file_url": filedata.file_url, "file_url": filedata.file_url,
"comment": comment.as_dict()
"comment": comment.as_dict() if comment else {}
} }


def save_uploaded(dt, dn):
def save_uploaded(dt, dn, folder):
fname, content = get_uploaded_content() fname, content = get_uploaded_content()
if content: if content:
return save_file(fname, content, dt, dn);
return save_file(fname, content, dt, dn, folder);
else: else:
raise Exception raise Exception


def save_url(file_url, dt, dn):
def save_url(file_url, dt, dn, folder):
# if not (file_url.startswith("http://") or file_url.startswith("https://")): # if not (file_url.startswith("http://") or file_url.startswith("https://")):
# frappe.msgprint("URL must start with 'http://' or 'https://'") # frappe.msgprint("URL must start with 'http://' or 'https://'")
# return None, None # return None, None


f = frappe.get_doc({ f = frappe.get_doc({
"doctype": "File Data",
"doctype": "File",
"file_url": file_url, "file_url": file_url,
"attached_to_doctype": dt, "attached_to_doctype": dt,
"attached_to_name": dn
"attached_to_name": dn,
"folder": folder
}) })
f.flags.ignore_permissions = True f.flags.ignore_permissions = True
try: try:
f.insert(); f.insert();
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
return frappe.get_doc("File Data", f.duplicate_entry)
return frappe.get_doc("File", f.duplicate_entry)
return f return f


def get_uploaded_content(): def get_uploaded_content():
@@ -128,7 +130,7 @@ def get_random_filename(extn=None, content_type=None):


return random_string(7) + (extn or "") return random_string(7) + (extn or "")


def save_file(fname, content, dt, dn, decode=False):
def save_file(fname, content, dt, dn, folder=None, decode=False):
if decode: if decode:
if isinstance(content, unicode): if isinstance(content, unicode):
content = content.encode("utf-8") content = content.encode("utf-8")
@@ -148,9 +150,10 @@ def save_file(fname, content, dt, dn, decode=False):
file_data = copy(file_data) file_data = copy(file_data)


file_data.update({ file_data.update({
"doctype": "File Data",
"doctype": "File",
"attached_to_doctype": dt, "attached_to_doctype": dt,
"attached_to_name": dn, "attached_to_name": dn,
"folder": folder,
"file_size": file_size, "file_size": file_size,
"content_hash": content_hash, "content_hash": content_hash,
}) })
@@ -160,12 +163,12 @@ def save_file(fname, content, dt, dn, decode=False):
try: try:
f.insert(); f.insert();
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
return frappe.get_doc("File Data", f.duplicate_entry)
return frappe.get_doc("File", f.duplicate_entry)
return f return f


def get_file_data_from_hash(content_hash): def get_file_data_from_hash(content_hash):
for name in frappe.db.sql_list("select name from `tabFile Data` where content_hash=%s", content_hash):
b = frappe.get_doc('File Data', name)
for name in frappe.db.sql_list("select name from `tabFile` where content_hash=%s", content_hash):
b = frappe.get_doc('File', name)
return {k:b.get(k) for k in frappe.get_hooks()['write_file_keys']} return {k:b.get(k) for k in frappe.get_hooks()['write_file_keys']}
return False return False


@@ -201,7 +204,7 @@ def write_file(content, file_path, fname):
def remove_all(dt, dn): def remove_all(dt, dn):
"""remove all files in a transaction""" """remove all files in a transaction"""
try: try:
for fid in frappe.db.sql_list("""select name from `tabFile Data` where
for fid in frappe.db.sql_list("""select name from `tabFile` where
attached_to_doctype=%s and attached_to_name=%s""", (dt, dn)): attached_to_doctype=%s and attached_to_name=%s""", (dt, dn)):
remove_file(fid, dt, dn) remove_file(fid, dt, dn)
except Exception, e: except Exception, e:
@@ -209,19 +212,19 @@ def remove_all(dt, dn):


def remove_file_by_url(file_url, doctype=None, name=None): def remove_file_by_url(file_url, doctype=None, name=None):
if doctype and name: if doctype and name:
fid = frappe.db.get_value("File Data", {"file_url": file_url,
fid = frappe.db.get_value("File", {"file_url": file_url,
"attached_to_doctype": doctype, "attached_to_name": name}) "attached_to_doctype": doctype, "attached_to_name": name})
else: else:
fid = frappe.db.get_value("File Data", {"file_url": file_url})
fid = frappe.db.get_value("File", {"file_url": file_url})


if fid: if fid:
return remove_file(fid) return remove_file(fid)


def remove_file(fid, attached_to_doctype=None, attached_to_name=None): def remove_file(fid, attached_to_doctype=None, attached_to_name=None):
"""Remove file and File Data entry"""
"""Remove file and File entry"""
file_name = None file_name = None
if not (attached_to_doctype and attached_to_name): if not (attached_to_doctype and attached_to_name):
attached = frappe.db.get_value("File Data", fid,
attached = frappe.db.get_value("File", fid,
["attached_to_doctype", "attached_to_name", "file_name"]) ["attached_to_doctype", "attached_to_name", "file_name"])
if attached: if attached:
attached_to_doctype, attached_to_name, file_name = attached attached_to_doctype, attached_to_name, file_name = attached
@@ -231,10 +234,10 @@ def remove_file(fid, attached_to_doctype=None, attached_to_name=None):
doc = frappe.get_doc(attached_to_doctype, attached_to_name) doc = frappe.get_doc(attached_to_doctype, attached_to_name)
ignore_permissions = doc.has_permission("write") or False ignore_permissions = doc.has_permission("write") or False
if not file_name: if not file_name:
file_name = frappe.db.get_value("File Data", fid, "file_name")
file_name = frappe.db.get_value("File", fid, "file_name")
comment = doc.add_comment("Attachment Removed", _("Removed {0}").format(file_name)) comment = doc.add_comment("Attachment Removed", _("Removed {0}").format(file_name))


frappe.delete_doc("File Data", fid, ignore_permissions=ignore_permissions)
frappe.delete_doc("File", fid, ignore_permissions=ignore_permissions)


return comment return comment


@@ -256,7 +259,7 @@ def delete_file_from_filesystem(doc):
os.remove(path) os.remove(path)


def get_file(fname): def get_file(fname):
f = frappe.db.sql("""select file_name from `tabFile Data`
f = frappe.db.sql("""select file_name from `tabFile`
where name=%s or file_name=%s""", (fname, fname)) where name=%s or file_name=%s""", (fname, fname))
if f: if f:
file_name = f[0][0] file_name = f[0][0]
@@ -281,7 +284,7 @@ def get_file_name(fname, optional_suffix):
# convert to unicode # convert to unicode
fname = cstr(fname) fname = cstr(fname)


n_records = frappe.db.sql("select name from `tabFile Data` where file_name=%s", fname)
n_records = frappe.db.sql("select name from `tabFile` where file_name=%s", fname)
if len(n_records) > 0 or os.path.exists(encode(get_files_path(fname))): if len(n_records) > 0 or os.path.exists(encode(get_files_path(fname))):
f = fname.rsplit('.', 1) f = fname.rsplit('.', 1)
if len(f) == 1: if len(f) == 1:
@@ -290,4 +293,3 @@ def get_file_name(fname, optional_suffix):
partial, extn = f[0], "." + f[1] partial, extn = f[0], "." + f[1]
return '{partial}{suffix}{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix) return '{partial}{suffix}{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix)
return fname return fname


+ 3
- 0
frappe/utils/install.py Переглянути файл

@@ -39,6 +39,9 @@ def after_install():
{'doctype': "Email Account", "email_id": "replies@example.com", "default_incoming": 1} {'doctype': "Email Account", "email_id": "replies@example.com", "default_incoming": 1}
] ]


from frappe.core.doctype.file.file import make_home_folder
make_home_folder()

for d in install_docs: for d in install_docs:
try: try:
frappe.get_doc(d).insert() frappe.get_doc(d).insert()


+ 3
- 0
frappe/utils/nestedset.py Переглянути файл

@@ -226,6 +226,9 @@ class NestedSet(Document):
(self.doctype, self.nsm_parent_field, '%s'), (self.name)): (self.doctype, self.nsm_parent_field, '%s'), (self.name)):
frappe.throw(_("{0} {1} cannot be a leaf node as it has children").format(_(self.doctype), self.name)) frappe.throw(_("{0} {1} cannot be a leaf node as it has children").format(_(self.doctype), self.name))


def get_ancestors(self):
return get_ancestors_of(self.doctype, self.name)

def get_root_of(doctype): def get_root_of(doctype):
"""Get root element of a DocType with a tree structure""" """Get root element of a DocType with a tree structure"""
return frappe.db.sql("""select t1.name from `tab{0}` t1 where return frappe.db.sql("""select t1.name from `tab{0}` t1 where


Завантаження…
Відмінити
Зберегти