Merge branch 'develop' into fix-browserlite-caniuse-lite-upgrade

This commit is contained in:
Suraj Shetty 2020-08-20 10:58:27 +05:30 committed by GitHub
commit 1cfda34d8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 407 additions and 151 deletions

View file

@ -609,7 +609,7 @@
"link_fieldname": "reference_doctype"
}
],
"modified": "2020-08-06 12:59:32.369093",
"modified": "2020-08-06 12:59:32.369095",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",

View file

@ -42,26 +42,6 @@ frappe.ui.form.on('Report', {
}
},
report_type: function(frm) {
frm.set_intro("");
switch(frm.doc.report_type) {
case "Report Builder":
frm.set_intro(__("Report Builder reports are managed directly by the report builder. Nothing to do."));
break;
case "Query Report":
frm.set_intro(__("Write a SELECT query. Note result is not paged (all data is sent in one go).")
+ __("To format columns, give column labels in the query.") + "<br>"
+ __("[Label]:[Field Type]/[Options]:[Width]") + "<br><br>"
+ __("Example:") + "<br>"
+ "Employee:Link/Employee:200" + "<br>"
+ "Rate:Currency:120" + "<br>")
break;
case "Script Report":
frm.set_intro(__("Write a Python file in the same folder where this is saved and return column and result."));
break;
}
},
set_doctype_roles: function(frm) {
return frm.call('set_doctype_roles').then(() => {
frm.refresh_field('roles');

View file

@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "field:report_name",
"creation": "2013-03-09 15:45:57",
"doctype": "DocType",
@ -17,10 +18,15 @@
"disabled",
"disable_prepared_report",
"prepared_report",
"filters_section",
"filters",
"columns_section",
"columns",
"section_break_6",
"query",
"javascript",
"report_script",
"client_code_section",
"javascript",
"json",
"permission_rules",
"roles"
@ -94,7 +100,8 @@
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Query / Script"
},
{
"depends_on": "eval:doc.report_type==\"Query Report\"",
@ -142,15 +149,50 @@
"read_only": 1
},
{
"depends_on": "eval:doc.report_type==\"Script Report\" && doc.is_standard===\"No\"",
"description": "output in the form of `data = [columns, result]`",
"depends_on": "eval:(doc.report_type===\"Script Report\" \n|| doc.report_type==\"Query Report\") \n&& doc.is_standard===\"No\"",
"description": "Filters will be accessible via <code>filters</code>. <br><br>Send output as <code>result = [result]</code>, or for old style <code>data = [columns], [result]</code>",
"fieldname": "report_script",
"fieldtype": "Code",
"label": "Script"
},
{
"collapsible": 1,
"collapsible_depends_on": "filters",
"fieldname": "filters_section",
"fieldtype": "Section Break",
"label": "Filters"
},
{
"fieldname": "filters",
"fieldtype": "Table",
"label": "Filters",
"options": "Report Filter"
},
{
"collapsible": 1,
"collapsible_depends_on": "columns",
"fieldname": "columns_section",
"fieldtype": "Section Break",
"label": "Columns"
},
{
"fieldname": "columns",
"fieldtype": "Table",
"label": "Columns",
"options": "Report Column"
},
{
"collapsible": 1,
"collapsible_depends_on": "javascript",
"fieldname": "client_code_section",
"fieldtype": "Section Break",
"label": "Client Code"
}
],
"idx": 1,
"modified": "2019-10-09 15:43:08.577610",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-08-17 16:49:28.474274",
"modified_by": "Administrator",
"module": "Core",
"name": "Report",

View file

@ -51,6 +51,9 @@ class Report(Document):
def on_trash(self):
delete_custom_role('report', self.name)
def get_columns(self):
return [d.as_dict(no_default_fields = True) for d in self.columns]
def set_doctype_roles(self):
if not self.get('roles') and self.is_standard == 'No':
meta = frappe.get_meta(self.ref_doctype)
@ -99,8 +102,8 @@ class Report(Document):
if not self.query.lower().startswith("select"):
frappe.throw(_("Query must be a SELECT"), title=_('Report Document Error'))
result = [list(t) for t in frappe.db.sql(self.query, filters)]
columns = [cstr(c[0]) for c in frappe.db.get_description()]
result = [list(t) for t in frappe.db.sql(self.query, filters, debug=True)]
columns = self.get_columns() or [cstr(c[0]) for c in frappe.db.get_description()]
return [columns, result]
@ -134,135 +137,167 @@ class Report(Document):
def execute_script(self, filters):
# server script
loc = {"filters": frappe._dict(filters), 'data':[]}
loc = {"filters": frappe._dict(filters), 'data':None, 'result':None}
safe_exec(self.report_script, None, loc)
return loc['data']
if loc['data']:
return loc['data']
else:
return self.get_columns(), loc['result']
def get_data(self, filters=None, limit=None, user=None, as_dict=False, ignore_prepared_report=False):
columns = []
out = []
if self.report_type in ('Query Report', 'Script Report', 'Custom Report'):
# query and script reports
data = frappe.desk.query_report.run(self.name,
filters=filters, user=user, ignore_prepared_report=ignore_prepared_report)
for d in data.get('columns'):
if isinstance(d, dict):
col = frappe._dict(d)
if not col.fieldname:
col.fieldname = col.label
columns.append(col)
else:
fieldtype, options = "Data", None
parts = d.split(':')
if len(parts) > 1:
if parts[1]:
fieldtype, options = parts[1], None
if fieldtype and '/' in fieldtype:
fieldtype, options = fieldtype.split('/')
columns.append(frappe._dict(label=parts[0], fieldtype=fieldtype, fieldname=parts[0], options=options))
out += data.get('result')
columns, result = self.run_query_report(filters, user, ignore_prepared_report)
else:
# standard report
params = json.loads(self.json)
if params.get('fields'):
columns = params.get('fields')
elif params.get('columns'):
columns = params.get('columns')
else:
columns = [['name', self.ref_doctype]]
for df in frappe.get_meta(self.ref_doctype).fields:
if df.in_list_view:
columns.append([df.fieldname, self.ref_doctype])
_filters = params.get('filters') or []
if filters:
for key, value in iteritems(filters):
condition, _value = '=', value
if isinstance(value, (list, tuple)):
condition, _value = value
_filters.append([key, condition, _value])
def _format(parts):
# sort by is saved as DocType.fieldname, covert it to sql
return '`tab{0}`.`{1}`'.format(*parts)
if params.get('sort_by'):
order_by = _format(params.get('sort_by').split('.')) + ' ' + params.get('sort_order')
elif params.get('order_by'):
order_by = params.get('order_by')
else:
order_by = _format([self.ref_doctype, 'modified']) + ' desc'
if params.get('sort_by_next'):
order_by += ', ' + _format(params.get('sort_by_next').split('.')) + ' ' + params.get('sort_order_next')
group_by = None
if params.get('group_by'):
group_by_args = frappe._dict(params['group_by'])
group_by = group_by_args['group_by']
order_by = '_aggregate_column desc'
result = frappe.get_list(self.ref_doctype,
fields = [
get_group_by_field(group_by_args, c[1]) if c[0] == '_aggregate_column' and group_by_args
else _format([c[1], c[0]])
for c in columns
],
filters=_filters,
order_by = order_by,
as_list=True,
limit=limit,
group_by=group_by,
user=user)
_columns = []
for (fieldname, doctype) in columns:
meta = frappe.get_meta(doctype)
if meta.get_field(fieldname):
field = meta.get_field(fieldname)
else:
if fieldname == '_aggregate_column':
label = get_group_by_column_label(group_by_args, meta)
else:
label = meta.get_label(fieldname)
field = frappe._dict(fieldname=fieldname, label=label)
# since name is the primary key for a document, it will always be a Link datatype
if fieldname == "name":
field.fieldtype = "Link"
field.options = doctype
_columns.append(field)
columns = _columns
out = out + [list(d) for d in result]
if params.get('add_totals_row'):
out = append_totals_row(out)
columns, result = self.run_standard_report(filters, limit, user)
if as_dict:
data = []
for row in out:
if isinstance(row, (list, tuple)):
_row = frappe._dict()
for i, val in enumerate(row):
_row[columns[i].get('fieldname')] = val
elif isinstance(row, dict):
# no need to convert from dict to dict
_row = frappe._dict(row)
data.append(_row)
else:
data = out
return columns, data
result = self.build_data_dict(result, columns)
return columns, result
def run_query_report(self, filters, user, ignore_prepared_report=False):
columns, result = [], []
data = frappe.desk.query_report.run(self.name,
filters=filters, user=user, ignore_prepared_report=ignore_prepared_report)
for d in data.get('columns'):
if isinstance(d, dict):
col = frappe._dict(d)
if not col.fieldname:
col.fieldname = col.label
columns.append(col)
else:
fieldtype, options = "Data", None
parts = d.split(':')
if len(parts) > 1:
if parts[1]:
fieldtype, options = parts[1], None
if fieldtype and '/' in fieldtype:
fieldtype, options = fieldtype.split('/')
columns.append(frappe._dict(label=parts[0], fieldtype=fieldtype, fieldname=parts[0], options=options))
result += data.get('result')
return columns, result
def run_standard_report(self, filters, limit, user):
params = json.loads(self.json)
columns = self.get_standard_report_columns(params)
result = []
order_by, group_by, group_by_args = self.get_standard_report_order_by(params)
_result = frappe.get_list(self.ref_doctype,
fields = [
get_group_by_field(group_by_args, c[1]) if c[0] == '_aggregate_column' and group_by_args
else Report._format([c[1], c[0]]) for c in columns
],
filters = self.get_standard_report_filters(params, filters),
order_by = order_by,
group_by = group_by,
as_list = True,
limit = limit,
user = user)
columns = self.build_standard_report_columns(columns, group_by_args)
result = result + [list(d) for d in _result]
if params.get('add_totals_row'):
result = append_totals_row(result)
return columns, result
@staticmethod
def _format(parts):
# sort by is saved as DocType.fieldname, covert it to sql
return '`tab{0}`.`{1}`'.format(*parts)
def get_standard_report_columns(self, params):
if params.get('fields'):
columns = params.get('fields')
elif params.get('columns'):
columns = params.get('columns')
elif params.get('fields'):
columns = params.get('fields')
else:
columns = [['name', self.ref_doctype]]
for df in frappe.get_meta(self.ref_doctype).fields:
if df.in_list_view:
columns.append([df.fieldname, self.ref_doctype])
return columns
def get_standard_report_filters(self, params, filters):
_filters = params.get('filters') or []
if filters:
for key, value in iteritems(filters):
condition, _value = '=', value
if isinstance(value, (list, tuple)):
condition, _value = value
_filters.append([key, condition, _value])
return _filters
def get_standard_report_order_by(self, params):
group_by_args = None
if params.get('sort_by'):
order_by = Report._format(params.get('sort_by').split('.')) + ' ' + params.get('sort_order')
elif params.get('order_by'):
order_by = params.get('order_by')
else:
order_by = Report._format([self.ref_doctype, 'modified']) + ' desc'
if params.get('sort_by_next'):
order_by += ', ' + Report._format(params.get('sort_by_next').split('.')) + ' ' + params.get('sort_order_next')
group_by = None
if params.get('group_by'):
group_by_args = frappe._dict(params['group_by'])
group_by = group_by_args['group_by']
order_by = '_aggregate_column desc'
return order_by, group_by, group_by_args
def build_standard_report_columns(self, columns, group_by_args):
_columns = []
for (fieldname, doctype) in columns:
meta = frappe.get_meta(doctype)
if meta.get_field(fieldname):
field = meta.get_field(fieldname)
else:
if fieldname == '_aggregate_column':
label = get_group_by_column_label(group_by_args, meta)
else:
label = meta.get_label(fieldname)
field = frappe._dict(fieldname=fieldname, label=label)
# since name is the primary key for a document, it will always be a Link datatype
if fieldname == "name":
field.fieldtype = "Link"
field.options = doctype
_columns.append(field)
return _columns
def build_data_dict(self, result, columns):
data = []
for row in result:
if isinstance(row, (list, tuple)):
_row = frappe._dict()
for i, val in enumerate(row):
_row[columns[i].get('fieldname')] = val
elif isinstance(row, dict):
# no need to convert from dict to dict
_row = frappe._dict(row)
data.append(_row)
return data
@Document.whitelist
def toggle_disable(self, disable):

View file

@ -111,3 +111,41 @@ data = [
# check values
self.assertTrue('System User' in [d.get('type') for d in data[1]])
def test_script_report_with_columns(self):
report_name = 'Test Script Report With Columns'
if frappe.db.exists("Report", report_name):
frappe.delete_doc('Report', report_name)
report = frappe.get_doc({
'doctype': 'Report',
'ref_doctype': 'User',
'report_name': report_name,
'report_type': 'Script Report',
'is_standard': 'No',
'columns': [
dict(fieldname='type', label='Type', fieldtype='Data'),
dict(fieldname='value', label='Value', fieldtype='Int'),
]
}).insert(ignore_permissions=True)
report.report_script = '''
totals = {}
for user in frappe.get_all('User', fields = ['name', 'user_type', 'creation']):
if not user.user_type in totals:
totals[user.user_type] = 0
totals[user.user_type] = totals[user.user_type] + 1
result = [
{"type":key, "value": value} for key, value in totals.items()
]
'''
report.save()
data = report.get_data()
# check columns
self.assertEqual(data[0][0]['label'], 'Type')
# check values
self.assertTrue('System User' in [d.get('type') for d in data[1]])

View file

@ -0,0 +1,61 @@
{
"actions": [],
"creation": "2020-01-14 11:28:37.583656",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"fieldname",
"label",
"fieldtype",
"options",
"width"
],
"fields": [
{
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Fieldname",
"reqd": 1
},
{
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Label",
"reqd": 1
},
{
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Fieldtype",
"options": "Check\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nInt\nLink\nSelect\nTime",
"reqd": 1
},
{
"fieldname": "options",
"fieldtype": "Data",
"label": "Options"
},
{
"fieldname": "width",
"fieldtype": "Int",
"label": "Width"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-08-17 14:32:17.174796",
"modified_by": "Administrator",
"module": "Core",
"name": "Report Column",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class ReportColumn(Document):
pass

View file

@ -0,0 +1,71 @@
{
"actions": [],
"creation": "2020-01-14 11:38:58.016498",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"fieldname",
"label",
"fieldtype",
"mandatory",
"options",
"wildcard_filter"
],
"fields": [
{
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Fieldname",
"reqd": 1
},
{
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Label",
"reqd": 1
},
{
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Fieldtype",
"options": "Check\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nInt\nLink\nSelect\nTime",
"reqd": 1
},
{
"default": "0",
"fieldname": "mandatory",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Mandatory"
},
{
"fieldname": "options",
"fieldtype": "Data",
"label": "Options"
},
{
"default": "0",
"description": "Will add \"%\" before and after the query",
"fieldname": "wildcard_filter",
"fieldtype": "Check",
"label": "Wildcard Filter"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-08-17 16:15:46.937267",
"modified_by": "Administrator",
"module": "Core",
"name": "Report Filter",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class ReportFilter(Document):
pass

View file

@ -366,6 +366,12 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
this.report_settings.html_format = settings.html_format;
this.report_settings.execution_time = settings.execution_time || 0;
frappe.query_reports[this.report_name] = this.report_settings;
if (this.report_doc.filters && !this.report_settings.filters) {
// add configured filters
this.report_settings.filters = this.report_doc.filters;
}
resolve();
});
}).catch(reject);
@ -1109,8 +1115,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
.map(f => {
var v = f.get_value();
// hidden fields dont have $input
if(f.df.hidden) v = f.value;
if(v === '%') v = null;
if (f.df.hidden) v = f.value;
if (v === '%') v = null;
if (f.df.wildcard_filter) {
v = `%${v}%`;
}
return {
[f.df.fieldname]: v
};