Merge branch 'develop' into version-12

This commit is contained in:
Sahil Khan 2019-09-20 15:14:17 +05:30
commit 61bd8b71ad
23 changed files with 323 additions and 669 deletions

View file

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

View file

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

View file

@ -53,5 +53,7 @@ class TestComment(unittest.TestCase):
reference_name = test_blog.name
))), 0)
test_blog.delete()

View file

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

View file

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

View file

@ -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("<a href='{file_url}' target='_blank'>{file_name}</a>{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):

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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:<br><br> <b>{{otp}}</b>'
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.<br><br> {{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)