diff --git a/.mergify.yml b/.mergify.yml
index b0e11e0e6d..d8f1c09bee 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -7,7 +7,21 @@ pull_request_rules:
- status-success=security/snyk - package.json (frappe)
- status-success=security/snyk - requirements.txt (frappe)
- label!=don't-merge
+ - label!=squash
- "#approved-reviews-by>=1"
actions:
merge:
method: merge
+ - name: Automatic squash on CI success and review
+ conditions:
+ - status-success=Codacy/PR Quality Review
+ - status-success=Semantic Pull Request
+ - status-success=continuous-integration/travis-ci/pr
+ - status-success=security/snyk - package.json (frappe)
+ - status-success=security/snyk - requirements.txt (frappe)
+ - label!=don't-merge
+ - label=squash
+ - "#approved-reviews-by>=1"
+ actions:
+ merge:
+ method: squash
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 78e0cdb355..8ea5148873 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -23,7 +23,7 @@ if sys.version[0] == '2':
reload(sys)
sys.setdefaultencoding("utf-8")
-__version__ = '12.0.13'
+__version__ = '12.0.14'
__title__ = "Frappe Framework"
local = Local()
diff --git a/frappe/core/doctype/comment/test_comment.py b/frappe/core/doctype/comment/test_comment.py
index 2adc5eb899..3cf8fbaa3f 100644
--- a/frappe/core/doctype/comment/test_comment.py
+++ b/frappe/core/doctype/comment/test_comment.py
@@ -53,5 +53,7 @@ class TestComment(unittest.TestCase):
reference_name = test_blog.name
))), 0)
+ test_blog.delete()
+
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index eb4424db30..bfc5ba845a 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -586,9 +586,11 @@ class DocType(Document):
if not self.get('is_tree'):
return
self.add_nestedset_fields()
- # set field as mandatory
- field = self.meta.get_field('nsm_parent_field')
- field.reqd = 1
+
+ if not self.nsm_parent_field:
+ field_label = frappe.bold(_("Parent Field (Tree)"))
+ frappe.throw(_("{0} is a mandatory field").format(field_label), frappe.MandatoryError)
+
# check if field is valid
fieldnames = [df.fieldname for df in self.fields]
if self.nsm_parent_field and self.nsm_parent_field not in fieldnames:
diff --git a/frappe/core/doctype/file/file.json b/frappe/core/doctype/file/file.json
index 9bf7d03512..d9ab504db7 100644
--- a/frappe/core/doctype/file/file.json
+++ b/frappe/core/doctype/file/file.json
@@ -22,8 +22,6 @@
"column_break_10",
"attached_to_name",
"attached_to_field",
- "lft",
- "rgt",
"old_parent",
"content_hash",
"uploaded_to_dropbox",
@@ -145,18 +143,6 @@
"label": "Attached To Field",
"read_only": 1
},
- {
- "fieldname": "lft",
- "fieldtype": "Int",
- "hidden": 1,
- "label": "lft"
- },
- {
- "fieldname": "rgt",
- "fieldtype": "Int",
- "hidden": 1,
- "label": "rgt"
- },
{
"fieldname": "old_parent",
"fieldtype": "Data",
@@ -186,7 +172,7 @@
],
"icon": "fa fa-file",
"idx": 1,
- "modified": "2019-08-16 16:41:03.086023",
+ "modified": "2019-08-30 19:46:20.796453",
"modified_by": "Administrator",
"module": "Core",
"name": "File",
diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py
index 3c89519faf..fa80d0ed6e 100755
--- a/frappe/core/doctype/file/file.py
+++ b/frappe/core/doctype/file/file.py
@@ -26,6 +26,7 @@ from frappe.utils import get_hook_method, get_files_path, random_string, encode,
from frappe import _
from frappe import conf
from frappe.utils.nestedset import NestedSet
+from frappe.model.document import Document
from frappe.utils import strip
from PIL import Image, ImageOps
from six import StringIO, string_types
@@ -42,8 +43,7 @@ class FolderNotEmpty(frappe.ValidationError): pass
exclude_from_linked_with = True
-class File(NestedSet):
- nsm_parent_field = 'folder'
+class File(Document):
no_feed_on_delete = True
def before_insert(self):
@@ -72,8 +72,6 @@ class File(NestedSet):
self.name = frappe.generate_hash("", 10)
def after_insert(self):
- self.update_parent_folder_size()
-
if not self.is_folder:
self.add_comment_in_reference_doc('Attachment',
_('Added {0}').format("{file_name}{icon}".format(**{
@@ -100,7 +98,6 @@ class File(NestedSet):
self.validate_file()
self.generate_content_hash()
- self.set_folder_size()
self.validate_url()
if frappe.db.exists('File', {'name': self.name, 'is_folder': 0}):
@@ -136,31 +133,6 @@ class File(NestedSet):
frappe.db.set_value(self.attached_to_doctype, self.attached_to_name,
self.attached_to_field, self.file_url)
-
- def set_folder_size(self):
- """Set folder size if folder"""
- if self.is_folder and not self.is_new():
- self.file_size = cint(self.get_folder_size())
- self.db_set('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 ifnull(sum(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).set_folder_size()
-
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:
@@ -232,7 +204,6 @@ class File(NestedSet):
if self.is_home_folder or self.is_attachments_folder:
frappe.throw(_("Cannot delete Home and Attachments folders"))
self.check_folder_is_empty()
- super(File, self).on_trash()
self.call_delete_file()
if not self.is_folder:
self.add_comment_in_reference_doc('Attachment Removed', _("Removed {0}").format(self.file_name))
@@ -274,9 +245,6 @@ class File(NestedSet):
return thumbnail_url
- def after_delete(self):
- self.update_parent_folder_size()
-
def check_folder_is_empty(self):
"""Throw exception if folder is not empty"""
files = frappe.get_all("File", filters={"folder": self.name}, fields=("name", "file_name"))
@@ -598,7 +566,6 @@ class File(NestedSet):
def on_doctype_update():
frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"])
- frappe.db.add_index("File", ["lft", "rgt"])
def make_home_folder():
home = frappe.get_doc({
@@ -619,12 +586,16 @@ def make_home_folder():
@frappe.whitelist()
def get_breadcrumbs(folder):
"""returns name, file_name of parent folder"""
- values = frappe.db.get_value("File", folder, ["lft", "rgt"], as_dict=True)
- if not values:
- frappe.throw(_("Folder {0} does not exist").format(folder))
+ path = folder.split('/')
- return frappe.db.sql("""select name, file_name from tabFile
- where lft < %s and rgt > %s order by lft asc""", (values.lft, values.rgt), as_dict=1)
+ folders = []
+ for i, _ in enumerate(path):
+ indexes = range(0, i)
+ folder = '/'.join([path[i] for i in indexes])
+ if folder:
+ folders.append(folder)
+
+ return [frappe._dict(file_name=f) for f in folders]
@frappe.whitelist()
def create_new_folder(file_name, folder):
diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py
index e3c48e7469..be68f1fee2 100644
--- a/frappe/core/doctype/file/test_file.py
+++ b/frappe/core/doctype/file/test_file.py
@@ -183,7 +183,7 @@ class TestFile(unittest.TestCase):
def delete_test_data(self):
for f in frappe.db.sql('''select name, file_name from tabFile where
- is_home_folder = 0 and is_attachments_folder = 0 order by rgt-lft asc'''):
+ is_home_folder = 0 and is_attachments_folder = 0 order by creation desc'''):
frappe.delete_doc("File", f[0])
@@ -212,11 +212,8 @@ class TestFile(unittest.TestCase):
def tests_after_upload(self):
self.assertEqual(self.saved_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_name, "file_size")
-
- self.assertEqual(folder_size, saved_file_size)
+ file_folder = frappe.db.get_value("File", self.saved_name, "folder")
+ self.assertEqual(file_folder, _("Home/Test Folder 1"))
def test_file_copy(self):
@@ -227,8 +224,6 @@ class TestFile(unittest.TestCase):
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"), 0)
def test_folder_copy(self):
@@ -251,8 +246,6 @@ class TestFile(unittest.TestCase):
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"), 0)
def test_default_folder(self):
diff --git a/frappe/core/doctype/prepared_report/prepared_report.json b/frappe/core/doctype/prepared_report/prepared_report.json
index ec89c6327a..ab6650d9e3 100644
--- a/frappe/core/doctype/prepared_report/prepared_report.json
+++ b/frappe/core/doctype/prepared_report/prepared_report.json
@@ -1,463 +1,128 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "REP.#####",
- "beta": 0,
- "creation": "2018-06-25 18:39:11.152960",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "autoname": "REP.#####",
+ "creation": "2018-06-25 18:39:11.152960",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "report_name",
+ "ref_report_doctype",
+ "status",
+ "column_break_4",
+ "report_start_time",
+ "report_end_time",
+ "section_break_7",
+ "error_message",
+ "filters_sb",
+ "filters",
+ "filter_values",
+ "columns"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "report_name",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Report Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "report_name",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Report Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "ref_report_doctype",
- "fieldtype": "Link",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Ref Report DocType",
- "length": 0,
- "no_copy": 0,
- "options": "Report",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "ref_report_doctype",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Ref Report DocType",
+ "options": "Report",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Queued",
- "fetch_if_empty": 0,
- "fieldname": "status",
- "fieldtype": "Select",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "Error\nQueued\nCompleted",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Queued",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "in_list_view": 1,
+ "label": "Status",
+ "options": "Error\nQueued\nCompleted",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "report_start_time",
- "fieldtype": "Datetime",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Report Start Time",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "report_start_time",
+ "fieldtype": "Datetime",
+ "label": "Report Start Time",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "report_end_time",
- "fieldtype": "Datetime",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Report End Time",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "report_end_time",
+ "fieldtype": "Datetime",
+ "label": "Report End Time",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.status == 'Error'",
- "fetch_if_empty": 0,
- "fieldname": "section_break_7",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:doc.status == 'Error'",
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "error_message",
- "fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Error Message",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "error_message",
+ "fieldtype": "Text",
+ "label": "Error Message",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "filters_sb",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Filters",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "filters_sb",
+ "fieldtype": "Section Break",
+ "label": "Filters"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "filters",
- "fieldtype": "Small Text",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Filters",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "filters",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Filters",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "filter_values",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Filter Values",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "filter_values",
+ "fieldtype": "HTML",
+ "label": "Filter Values"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "columns",
- "fieldtype": "Code",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Columns",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "columns",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Columns",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-04-19 12:39:47.211516",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Prepared Report",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "in_create": 1,
+ "modified": "2019-09-18 04:00:55.644257",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Prepared Report",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "report_name",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "report_name",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py
index 29c069515f..989a99511a 100644
--- a/frappe/core/doctype/prepared_report/prepared_report.py
+++ b/frappe/core/doctype/prepared_report/prepared_report.py
@@ -4,32 +4,25 @@
from __future__ import unicode_literals
-import base64
+
import json
-import io
import frappe
-from frappe.model.document import Document
-from frappe.utils.background_jobs import enqueue
-from frappe.desk.query_report import generate_report_result, get_columns_dict
-from frappe.core.doctype.file.file import remove_all
-from frappe.utils.csvutils import to_csv, read_csv_content_from_attached_file
from frappe.desk.form.load import get_attachments
+from frappe.desk.query_report import generate_report_result
+from frappe.model.document import Document
from frappe.utils import gzip_compress, gzip_decompress
-from six import PY2
-from frappe.utils import encode
+from frappe.utils.background_jobs import enqueue
+from frappe.core.doctype.file.file import remove_all
+
class PreparedReport(Document):
-
def before_insert(self):
self.status = "Queued"
self.report_start_time = frappe.utils.now()
def enqueue_report(self):
- enqueue(
- run_background,
- prepared_report=self.name, timeout=6000
- )
+ enqueue(run_background, prepared_report=self.name, timeout=6000)
def on_trash(self):
remove_all("PreparedReport", self.name, from_delete=True)
@@ -42,14 +35,18 @@ def run_background(prepared_report):
try:
report.custom_columns = []
- if report.report_type == 'Custom Report':
+ if report.report_type == "Custom Report":
custom_report_doc = report
reference_report = custom_report_doc.reference_report
report = frappe.get_doc("Report", reference_report)
report.custom_columns = custom_report_doc.json
- result = generate_report_result(report, filters=instance.filters, user=instance.owner)
- create_json_gz_file(result['result'], 'Prepared Report', instance.name)
+ result = generate_report_result(
+ report=report,
+ filters=instance.filters,
+ user=instance.owner
+ )
+ create_json_gz_file(result["result"], "Prepared Report", instance.name)
instance.status = "Completed"
instance.columns = json.dumps(result["columns"])
@@ -64,8 +61,11 @@ def run_background(prepared_report):
instance.save(ignore_permissions=True)
frappe.publish_realtime(
- 'report_generated',
- {"report_name": instance.report_name, "name": instance.name},
+ "report_generated",
+ {
+ "report_name": instance.report_name,
+ "name": instance.name
+ },
user=frappe.session.user
)
@@ -73,7 +73,9 @@ def run_background(prepared_report):
def create_json_gz_file(data, dt, dn):
# Storing data in CSV file causes information loss
# Reports like P&L Statement were completely unsuable because of this
- json_filename = '{0}.json.gz'.format(frappe.utils.data.format_datetime(frappe.utils.now(), "Y-m-d-H:M"))
+ json_filename = "{0}.json.gz".format(
+ frappe.utils.data.format_datetime(frappe.utils.now(), "Y-m-d-H:M")
+ )
encoded_content = frappe.safe_encode(frappe.as_json(data))
compressed_content = gzip_compress(encoded_content)
@@ -87,10 +89,11 @@ def create_json_gz_file(data, dt, dn):
})
_file.save()
+
@frappe.whitelist()
def download_attachment(dn):
attachment = get_attachments("Prepared Report", dn)[0]
frappe.local.response.filename = attachment.file_name[:-2]
- attached_file = frappe.get_doc('File', attachment.name)
+ attached_file = frappe.get_doc("File", attachment.name)
frappe.local.response.filecontent = gzip_decompress(attached_file.get_content())
frappe.local.response.type = "binary"
diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py
index 1467eafc5d..7ce2537da3 100644
--- a/frappe/core/doctype/role/role.py
+++ b/frappe/core/doctype/role/role.py
@@ -31,7 +31,7 @@ def get_emails_from_role(role):
for user in users:
user_email, enabled = frappe.db.get_value("User", user.parent, ["email", "enabled"])
- if enabled:
+ if enabled and user_email not in ["admin@example.com", "guest@example.com"]:
emails.append(user_email)
return emails
\ No newline at end of file
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 6702ce2a5e..412051c76f 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -758,7 +758,10 @@ class Database(object):
def field_exists(self, dt, fn):
"""Return true of field exists."""
- return self.sql("select name from tabDocField where fieldname=%s and parent=%s", (dt, fn))
+ return self.exists('DocField', {
+ 'fieldname': fn,
+ 'parent': dt
+ })
def table_exists(self, doctype):
"""Returns True if table for given doctype exists."""
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index e3e408eeb1..dd984625fd 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -213,6 +213,8 @@ def get_labels(fields, doctype):
for key in fields:
key = key.split(" as ")[0]
+ if key.startswith(('count(', 'sum(', 'avg(')): continue
+
if "." in key:
parenttype, fieldname = key.split(".")[0][4:-1], key.split(".")[1].strip("`")
else:
diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py
index 8032e020d7..f8d3002a2c 100644
--- a/frappe/email/doctype/notification/notification.py
+++ b/frappe/email/doctype/notification/notification.py
@@ -309,9 +309,7 @@ def evaluate_alert(doc, alert, event):
else:
raise
db_value = parse_val(db_value)
- if (doc.get(alert.value_changed) == db_value) or \
- (not db_value and not doc.get(alert.value_changed)):
-
+ if (doc.get(alert.value_changed) == db_value) or (not db_value and not doc.get(alert.value_changed)):
return # value not changed
if event != "Value Change" and not doc.is_new():
diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py
index 7480970711..6eb2efce6b 100644
--- a/frappe/model/base_document.py
+++ b/frappe/model/base_document.py
@@ -38,8 +38,8 @@ def get_controller(doctype):
or ["Core", False]
if custom:
- if frappe.db.field_exists(doctype, "is_tree"):
- is_tree = frappe.db.get_value("DocType", doctype, ("is_tree"), cache=True)
+ if frappe.db.field_exists("DocType", "is_tree"):
+ is_tree = frappe.db.get_value("DocType", doctype, "is_tree", cache=True)
else:
is_tree = False
_class = NestedSet if is_tree else Document
diff --git a/frappe/permissions.py b/frappe/permissions.py
index a27ba5ee75..a0d1677fac 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -423,7 +423,7 @@ def clear_user_permissions_for_doctype(doctype, user=None):
filters = {'allow': doctype}
if user:
filters['user'] = user
- user_permissions_for_doctype = frappe.db.get_list('User Permission', filters=filters)
+ user_permissions_for_doctype = frappe.db.get_all('User Permission', filters=filters)
for d in user_permissions_for_doctype:
frappe.delete_doc('User Permission', d.name)
diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js
index 38250f2ad8..e680c8ada1 100644
--- a/frappe/public/js/frappe/form/quick_entry.js
+++ b/frappe/public/js/frappe/form/quick_entry.js
@@ -186,7 +186,9 @@ frappe.ui.form.QuickEntryForm = Class.extend({
}
},
error: function() {
- me.open_doc();
+ if (!me.skip_redirect_on_error) {
+ me.open_doc();
+ }
},
always: function() {
me.dialog.working = false;
diff --git a/frappe/public/js/frappe/views/file/file_view.js b/frappe/public/js/frappe/views/file/file_view.js
index 6ee37aec3d..5f9ca642f4 100644
--- a/frappe/public/js/frappe/views/file/file_view.js
+++ b/frappe/public/js/frappe/views/file/file_view.js
@@ -259,7 +259,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView {
get_left_html(file) {
file = this.prepare_datum(file);
- const file_size = frappe.form.formatters.FileSize(file.file_size);
+ const file_size = file.file_size ? frappe.form.formatters.FileSize(file.file_size) : '';
const route_url = this.get_route_url(file);
let created_on;
diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js
index ad4380bb4a..6ac26c00da 100644
--- a/frappe/public/js/frappe/views/reports/report_view.js
+++ b/frappe/public/js/frappe/views/reports/report_view.js
@@ -1231,23 +1231,27 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
action: () => {
const args = this.get_args();
const selected_items = this.get_checked_items(true);
+ let fields = [
+ {
+ fieldtype: 'Select',
+ label: __('Select File Type'),
+ fieldname:'file_format_type',
+ options: ['Excel', 'CSV'],
+ default: 'Excel'
+ }
+ ];
+
+ if (this.total_count > args.page_length) {
+ fields.push({
+ fieldtype: 'Check',
+ fieldname: 'export_all_rows',
+ label: __('Export All {0} rows?', [(this.total_count + "").bold()])
+ });
+ }
const d = new frappe.ui.Dialog({
title: __("Export Report: {0}",[__(this.doctype)]),
- fields: [
- {
- fieldtype: 'Select',
- label: __('Select File Type'),
- fieldname:'file_format_type',
- options: ['Excel', 'CSV'],
- default: 'Excel'
- },
- {
- fieldtype: 'Check',
- fieldname: 'export_all_rows',
- label: __('Export All {0} rows?', [(this.total_count + "").bold()])
- }
- ],
+ fields: fields,
primary_action_label: __('Download'),
primary_action: (data) => {
args.cmd = 'frappe.desk.reportview.export_query';
diff --git a/frappe/public/js/frappe/web_form/web_form.js b/frappe/public/js/frappe/web_form/web_form.js
index 216bf2d831..ca44dd65d0 100644
--- a/frappe/public/js/frappe/web_form/web_form.js
+++ b/frappe/public/js/frappe/web_form/web_form.js
@@ -107,7 +107,6 @@ export default class WebForm extends frappe.ui.FieldGroup {
let for_payment = Boolean(this.accept_payment && !this.doc.paid);
this.doc.doctype = this.doc_type;
- this.doc.name = this.doc_name;
this.doc.web_form_name = this.name;
// Save
diff --git a/frappe/public/js/frappe/web_form/web_form_list.js b/frappe/public/js/frappe/web_form/web_form_list.js
index 7b14844c6f..db5f1da3a0 100644
--- a/frappe/public/js/frappe/web_form/web_form_list.js
+++ b/frappe/public/js/frappe/web_form/web_form_list.js
@@ -201,14 +201,13 @@ export default class WebFormList {
() => (window.location.href = window.location.pathname + "?new=1")
);
- if (!(this.rows.length < this.page_length)) {
+ if (this.rows.length <= this.page_length) {
addButton(footer, "more", "secondary", false, "More", () => this.more());
}
function addButton(wrapper, id, type, hidden, name, action) {
const button = document.createElement("button");
- button.classList.add("btn", "btn-primary", "btn-sm", "ml-2");
- if (type == "secondary")
+ if (type == "secondary") {
button.classList.add(
"btn",
"btn-secondary",
@@ -216,7 +215,8 @@ export default class WebFormList {
"ml-2",
"text-white"
);
- if (type == "danger")
+ }
+ else if (type == "danger") {
button.classList.add(
"btn",
"btn-danger",
@@ -224,6 +224,10 @@ export default class WebFormList {
"btn-sm",
"ml-2"
);
+ }
+ else {
+ button.classList.add("btn", "btn-primary", "btn-sm", "ml-2");
+ }
button.id = id;
button.innerText = name;
diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py
index b1b12f642e..18c19cbf3e 100644
--- a/frappe/tests/test_db_query.py
+++ b/frappe/tests/test_db_query.py
@@ -185,37 +185,29 @@ class TestReportview(unittest.TestCase):
self.assertTrue('date_diff' in data[0])
def test_nested_permission(self):
- clear_user_permissions_for_doctype("File")
- delete_test_file_hierarchy() # delete already existing folders
- from frappe.core.doctype.file.file import create_new_folder
frappe.set_user('Administrator')
-
- create_new_folder('level1-A', 'Home')
- create_new_folder('level2-A', 'Home/level1-A')
- create_new_folder('level2-B', 'Home/level1-A')
- create_new_folder('level3-A', 'Home/level1-A/level2-A')
-
- create_new_folder('level1-B', 'Home')
- create_new_folder('level2-A', 'Home/level1-B')
+ create_nested_doctype()
+ create_nested_doctype_records()
+ clear_user_permissions_for_doctype('Nested DocType')
# user permission for only one root folder
- add_user_permission('File', 'Home/level1-A', 'test2@example.com')
+ add_user_permission('Nested DocType', 'Level 1 A', 'test2@example.com')
from frappe.core.page.permission_manager.permission_manager import update
- update('File', 'All', 0, 'if_owner', 0) # to avoid if_owner filter
+ # to avoid if_owner filter
+ update('Nested DocType', 'All', 0, 'if_owner', 0)
frappe.set_user('test2@example.com')
- data = DatabaseQuery("File").execute()
+ data = DatabaseQuery('Nested DocType').execute()
# children of root folder (for which we added user permission) should be accessible
- self.assertTrue({"name": "Home/level1-A/level2-A"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-B"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data)
+ self.assertTrue({'name': 'Level 2 A'} in data)
+ self.assertTrue({'name': 'Level 2 A'} in data)
# other folders should not be accessible
- self.assertFalse({"name": "Home/level1-B"} in data)
- self.assertFalse({"name": "Home/level1-B/level2-B"} in data)
- update('File', 'All', 0, 'if_owner', 1)
+ self.assertFalse({'name': 'Level 1 B'} in data)
+ self.assertFalse({'name': 'Level 2 B'} in data)
+ update('Nested DocType', 'All', 0, 'if_owner', 1)
frappe.set_user('Administrator')
def test_filter_sanitizer(self):
@@ -259,83 +251,74 @@ class TestReportview(unittest.TestCase):
self.assertTrue('DefaultValue' in [d['name'] for d in out])
def test_of_not_of_descendant_ancestors(self):
- clear_user_permissions_for_doctype("File")
- delete_test_file_hierarchy() # delete already existing folders
- from frappe.core.doctype.file.file import create_new_folder
-
- create_new_folder('level1-A', 'Home')
- create_new_folder('level2-A', 'Home/level1-A')
- create_new_folder('level2-B', 'Home/level1-A')
- create_new_folder('level3-A', 'Home/level1-A/level2-A')
-
- create_new_folder('level1-B', 'Home')
- create_new_folder('level2-A', 'Home/level1-B')
+ frappe.set_user('Administrator')
+ clear_user_permissions_for_doctype('Nested DocType')
# in descendants filter
- data = frappe.get_all('File', {'name': ('descendants of', 'Home/level1-A/level2-A')})
- self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data)
+ data = frappe.get_all('Nested DocType', {'name': ('descendants of', 'Level 2 A')})
+ self.assertTrue({"name": "Level 3 A"} in data)
- data = frappe.get_all('File', {'name': ('descendants of', 'Home/level1-A')})
- self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-A"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-B"} in data)
- self.assertFalse({"name": "Home/level1-B"} in data)
- self.assertFalse({"name": "Home/level1-A"} in data)
- self.assertFalse({"name": "Home"} in data)
+ data = frappe.get_all('Nested DocType', {'name': ('descendants of', 'Level 1 A')})
+ self.assertTrue({"name": "Level 3 A"} in data)
+ self.assertTrue({"name": "Level 2 A"} in data)
+ self.assertFalse({"name": "Level 2 B"} in data)
+ self.assertFalse({"name": "Level 1 B"} in data)
+ self.assertFalse({"name": "Level 1 A"} in data)
+ self.assertFalse({"name": "Root"} in data)
# in ancestors of filter
- data = frappe.get_all('File', {'name': ('ancestors of', 'Home/level1-A/level2-A')})
- self.assertFalse({"name": "Home/level1-A/level2-A/level3-A"} in data)
- self.assertFalse({"name": "Home/level1-A/level2-A"} in data)
- self.assertFalse({"name": "Home/level1-A/level2-B"} in data)
- self.assertFalse({"name": "Home/level1-B"} in data)
- self.assertTrue({"name": "Home/level1-A"} in data)
- self.assertTrue({"name": "Home"} in data)
+ data = frappe.get_all('Nested DocType', {'name': ('ancestors of', 'Level 2 A')})
+ self.assertFalse({"name": "Level 3 A"} in data)
+ self.assertFalse({"name": "Level 2 A"} in data)
+ self.assertFalse({"name": "Level 2 B"} in data)
+ self.assertFalse({"name": "Level 1 B"} in data)
+ self.assertTrue({"name": "Level 1 A"} in data)
+ self.assertTrue({"name": "Root"} in data)
- data = frappe.get_all('File', {'name': ('ancestors of', 'Home/level1-A')})
- self.assertFalse({"name": "Home/level1-A/level2-A/level3-A"} in data)
- self.assertFalse({"name": "Home/level1-A/level2-A"} in data)
- self.assertFalse({"name": "Home/level1-A/level2-B"} in data)
- self.assertFalse({"name": "Home/level1-B"} in data)
- self.assertFalse({"name": "Home/level1-A"} in data)
- self.assertTrue({"name": "Home"} in data)
+ data = frappe.get_all('Nested DocType', {'name': ('ancestors of', 'Level 1 A')})
+ self.assertFalse({"name": "Level 3 A"} in data)
+ self.assertFalse({"name": "Level 2 A"} in data)
+ self.assertFalse({"name": "Level 2 B"} in data)
+ self.assertFalse({"name": "Level 1 B"} in data)
+ self.assertFalse({"name": "Level 1 A"} in data)
+ self.assertTrue({"name": "Root"} in data)
# not descendants filter
- data = frappe.get_all('File', {'name': ('not descendants of', 'Home/level1-A/level2-A')})
- self.assertFalse({"name": "Home/level1-A/level2-A/level3-A"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-A"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-B"} in data)
- self.assertTrue({"name": "Home/level1-A"} in data)
- self.assertTrue({"name": "Home"} in data)
+ data = frappe.get_all('Nested DocType', {'name': ('not descendants of', 'Level 2 A')})
+ self.assertFalse({"name": "Level 3 A"} in data)
+ self.assertTrue({"name": "Level 2 A"} in data)
+ self.assertTrue({"name": "Level 2 B"} in data)
+ self.assertTrue({"name": "Level 1 A"} in data)
+ self.assertTrue({"name": "Root"} in data)
- data = frappe.get_all('File', {'name': ('not descendants of', 'Home/level1-A')})
- self.assertFalse({"name": "Home/level1-A/level2-A/level3-A"} in data)
- self.assertFalse({"name": "Home/level1-A/level2-A"} in data)
- self.assertFalse({"name": "Home/level1-A/level2-B"} in data)
- self.assertTrue({"name": "Home/level1-B"} in data)
- self.assertTrue({"name": "Home/level1-A"} in data)
- self.assertTrue({"name": "Home"} in data)
+ data = frappe.get_all('Nested DocType', {'name': ('not descendants of', 'Level 1 A')})
+ self.assertFalse({"name": "Level 3 A"} in data)
+ self.assertFalse({"name": "Level 2 A"} in data)
+ self.assertTrue({"name": "Level 2 B"} in data)
+ self.assertTrue({"name": "Level 1 B"} in data)
+ self.assertTrue({"name": "Level 1 A"} in data)
+ self.assertTrue({"name": "Root"} in data)
# not ancestors of filter
- data = frappe.get_all('File', {'name': ('not ancestors of', 'Home/level1-A/level2-A')})
- self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-A"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-B"} in data)
- self.assertTrue({"name": "Home/level1-B"} in data)
- self.assertTrue({"name": "Home/level1-A"} not in data)
- self.assertTrue({"name": "Home"} not in data)
+ data = frappe.get_all('Nested DocType', {'name': ('not ancestors of', 'Level 2 A')})
+ self.assertTrue({"name": "Level 3 A"} in data)
+ self.assertTrue({"name": "Level 2 A"} in data)
+ self.assertTrue({"name": "Level 2 B"} in data)
+ self.assertTrue({"name": "Level 1 B"} in data)
+ self.assertTrue({"name": "Level 1 A"} not in data)
+ self.assertTrue({"name": "Root"} not in data)
- data = frappe.get_all('File', {'name': ('not ancestors of', 'Home/level1-A')})
- self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-A"} in data)
- self.assertTrue({"name": "Home/level1-A/level2-B"} in data)
- self.assertTrue({"name": "Home/level1-B"} in data)
- self.assertTrue({"name": "Home/level1-A"} in data)
- self.assertFalse({"name": "Home"} in data)
+ data = frappe.get_all('Nested DocType', {'name': ('not ancestors of', 'Level 1 A')})
+ self.assertTrue({"name": "Level 3 A"} in data)
+ self.assertTrue({"name": "Level 2 A"} in data)
+ self.assertTrue({"name": "Level 2 B"} in data)
+ self.assertTrue({"name": "Level 1 B"} in data)
+ self.assertTrue({"name": "Level 1 A"} in data)
+ self.assertFalse({"name": "Root"} in data)
- data = frappe.get_all('File', {'name': ('ancestors of', 'Home')})
+ data = frappe.get_all('Nested DocType', {'name': ('ancestors of', 'Root')})
self.assertTrue(len(data) == 0)
- self.assertTrue(len(frappe.get_all('File', {'name': ('not ancestors of', 'Home')})) == len(frappe.get_all('File')))
+ self.assertTrue(len(frappe.get_all('Nested DocType', {'name': ('not ancestors of', 'Root')})) == len(frappe.get_all('Nested DocType')))
def test_is_set_is_not_set(self):
@@ -364,14 +347,45 @@ def create_event(subject="_Test Event", starts_on=None):
return event
-def delete_test_file_hierarchy():
- files_to_delete = [
- 'Home/level1-A/level2-A/level3-A',
- 'Home/level1-A/level2-A',
- 'Home/level1-A/level2-B',
- 'Home/level1-A',
- 'Home/level1-B/level2-A',
- 'Home/level1-B'
+def create_nested_doctype():
+ if frappe.db.exists('DocType', 'Nested DocType'):
+ return
+
+ frappe.get_doc({
+ 'doctype': 'DocType',
+ 'name': 'Nested DocType',
+ 'module': 'Custom',
+ 'is_tree': 1,
+ 'custom': 1,
+ 'autoname': 'Prompt',
+ 'fields': [
+ {'label': 'Description', 'fieldname': 'description'}
+ ],
+ 'permissions': [
+ {'role': 'Blogger'}
+ ]
+ }).insert()
+
+def create_nested_doctype_records():
+ '''
+ Create a structure like:
+ - Root
+ - Level 1 A
+ - Level 2 A
+ - Level 3 A
+ - Level 1 B
+ - Level 2 B
+ '''
+ records = [
+ {'name': 'Root', 'is_group': 1},
+ {'name': 'Level 1 A', 'parent_nested_doctype': 'Root', 'is_group': 1},
+ {'name': 'Level 2 A', 'parent_nested_doctype': 'Level 1 A', 'is_group': 1},
+ {'name': 'Level 3 A', 'parent_nested_doctype': 'Level 2 A'},
+ {'name': 'Level 1 B', 'parent_nested_doctype': 'Root', 'is_group': 1},
+ {'name': 'Level 2 B', 'parent_nested_doctype': 'Level 1 B'},
]
- for file_name in files_to_delete:
- frappe.delete_doc('File', file_name)
+
+ for r in records:
+ d = frappe.new_doc('Nested DocType')
+ d.update(r)
+ d.insert(ignore_permissions=True, ignore_if_duplicate=True)
diff --git a/frappe/tests/test_twofactor.py b/frappe/tests/test_twofactor.py
index 4064853dac..586748fcd3 100644
--- a/frappe/tests/test_twofactor.py
+++ b/frappe/tests/test_twofactor.py
@@ -7,8 +7,7 @@ from frappe.auth import HTTPRequest
from frappe.utils import cint
from frappe.tests import set_request
from frappe.twofactor import (should_run_2fa, authenticate_for_2factor, get_cached_user_pass,
- two_factor_is_enabled_for_, confirm_otp_token, get_otpsecret_for_, get_verification_obj,
- render_string_template)
+ two_factor_is_enabled_for_, confirm_otp_token, get_otpsecret_for_, get_verification_obj)
import time
@@ -123,7 +122,7 @@ class TestTwoFactor(unittest.TestCase):
'''String template renders as expected with variables.'''
args = {'issuer_name':'Frappe Technologies'}
_str = 'Verification Code from {{issuer_name}}'
- _str = render_string_template(_str,args)
+ _str = frappe.render_template(_str,args)
self.assertEqual(_str,'Verification Code from Frappe Technologies')
def test_bypass_restict_ip(self):
diff --git a/frappe/twofactor.py b/frappe/twofactor.py
index d8f68fb050..a539532f25 100644
--- a/frappe/twofactor.py
+++ b/frappe/twofactor.py
@@ -7,7 +7,6 @@ import frappe
from frappe import _
import pyotp, os
from frappe.utils.background_jobs import enqueue
-from jinja2 import Template
from pyqrcode import create as qrcreate
from six import BytesIO
from base64 import b64encode, b32encode
@@ -223,33 +222,27 @@ def process_2fa_for_email(user, token, otp_secret, otp_issuer, method='Email'):
def get_email_subject_for_2fa(kwargs_dict):
'''Get email subject for 2fa.'''
subject_template = _('Login Verification Code from {}').format(frappe.db.get_value('System Settings', 'System Settings', 'otp_issuer_name'))
- subject = render_string_template(subject_template, kwargs_dict)
+ subject = frappe.render_template(subject_template, kwargs_dict)
return subject
def get_email_body_for_2fa(kwargs_dict):
'''Get email body for 2fa.'''
body_template = 'Enter this code to complete your login:
{{otp}}'
- body = render_string_template(body_template, kwargs_dict)
+ body = frappe.render_template(body_template, kwargs_dict)
return body
def get_email_subject_for_qr_code(kwargs_dict):
'''Get QRCode email subject.'''
subject_template = _('One Time Password (OTP) Registration Code from {}').format(frappe.db.get_value('System Settings', 'System Settings', 'otp_issuer_name'))
- subject = render_string_template(subject_template, kwargs_dict)
+ subject = frappe.render_template(subject_template, kwargs_dict)
return subject
def get_email_body_for_qr_code(kwargs_dict):
'''Get QRCode email body.'''
body_template = 'Please click on the following link and follow the instructions on the page.
{{qrcode_link}}'
- body = render_string_template(body_template, kwargs_dict)
+ body = frappe.render_template(body_template, kwargs_dict)
return body
-def render_string_template(_str, kwargs_dict):
- '''Render string with jinja.'''
- s = Template(_str)
- s = s.render(**kwargs_dict)
- return s
-
def get_link_for_qrcode(user, totp_uri):
'''Get link to temporary page showing QRCode.'''
key = frappe.generate_hash(length=20)