diff --git a/frappe/auth.py b/frappe/auth.py index ab3624bee8..64fea36748 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -337,7 +337,7 @@ class CookieManager: if frappe.session.session_country: self.set_cookie("country", frappe.session.session_country) - def set_cookie(self, key, value, expires=None, secure=False, httponly=False, samesite="Strict"): + def set_cookie(self, key, value, expires=None, secure=False, httponly=False, samesite="Lax"): if not secure: secure = frappe.local.request.scheme == "https" self.cookies[key] = { diff --git a/frappe/commands/site.py b/frappe/commands/site.py index ec80ee019d..26eb455338 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -631,6 +631,29 @@ def stop_recording(context): if not context.sites: raise SiteNotSpecifiedError +@click.command('ngrok') +@pass_context +def start_ngrok(context): + from pyngrok import ngrok + + site = get_site(context) + frappe.init(site=site) + + port = frappe.conf.http_port or frappe.conf.webserver_port + public_url = ngrok.connect(port=port, options={ + 'host_header': site + }) + print(f'Public URL: {public_url}') + print('Inspect logs at http://localhost:4040') + + ngrok_process = ngrok.get_ngrok_process() + try: + # Block until CTRL-C or some other terminating event + ngrok_process.proc.wait() + except KeyboardInterrupt: + print("Shutting down server...") + frappe.destroy() + ngrok.kill() commands = [ add_system_manager, @@ -656,5 +679,6 @@ commands = [ browse, start_recording, stop_recording, - add_to_hosts + add_to_hosts, + start_ngrok ] diff --git a/frappe/desk/doctype/dashboard/dashboard.js b/frappe/desk/doctype/dashboard/dashboard.js index 9be6b55d53..237b549433 100644 --- a/frappe/desk/doctype/dashboard/dashboard.js +++ b/frappe/desk/doctype/dashboard/dashboard.js @@ -13,7 +13,6 @@ frappe.ui.form.on('Dashboard', { return { filters: { is_public: 1, - is_standard: 1, } }; }); @@ -22,7 +21,6 @@ frappe.ui.form.on('Dashboard', { return { filters: { is_public: 1, - is_standard: 1, } }; }); diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 6f071a6e2b..738d77ae27 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -175,6 +175,9 @@ frappe.ui.form.on('Dashboard Chart', { set_chart_field_options: function(frm) { let filters = frm.doc.filters_json.length > 2 ? JSON.parse(frm.doc.filters_json) : null; + if (frm.doc.dynamic_filters_json.length > 2) { + filters = {...filters, ...JSON.parse(frm.doc.dynamic_filters_json)}; + } frappe.xcall( 'frappe.desk.query_report.run', { @@ -193,7 +196,7 @@ frappe.ui.form.on('Dashboard Chart', { if (!frm.doc.is_custom) { if (data.result.length) { - frm.field_options = frappe.report_utils.get_possible_chart_options(data.columns, data); + frm.field_options = frappe.report_utils.get_field_options_from_report(data.columns, data); frm.set_df_property('x_field', 'options', frm.field_options.non_numeric_fields); if (!frm.field_options.numeric_fields.length) { frappe.msgprint(__(`Report has no numeric fields, please change the Report Name`)); @@ -435,49 +438,10 @@ frappe.ui.form.on('Dashboard Chart', { frm.trigger('set_dynamic_filters_in_table'); let filters = JSON.parse(frm.doc.filters_json || '[]'); - let fields = [ - { - fieldtype: 'HTML', - fieldname: 'description', - options: - `
-

Set dynamic filter values in JavaScript for the required fields here. -

-

Ex: - frappe.defaults.get_user_default("Company") -

-
` - } - ]; - if (is_document_type) { - if (frm.dynamic_filters) { - filters = [...filters, ...frm.dynamic_filters]; - } - filters.forEach(f => { - for (let field of fields) { - if (field.fieldname == f[0] + ':' + f[1]) { - return; - } - } - if (f[2] == '=') { - fields.push({ - label: `${f[1]} (${f[0]})`, - fieldname: f[0] + ':' + f[1], - fieldtype: 'Data', - }); - } - }); - } else { - filters = {...frm.dynamic_filters, ...filters}; - for (let key of Object.keys(filters)) { - fields.push({ - label: key, - fieldname: key, - fieldtype: 'Data', - }); - } - } + let fields = frappe.dashboard_utils.get_fields_for_dynamic_filter_dialog( + is_document_type, filters, frm.dynamic_filters + ); frm.dynamic_filter_table.on('click', () => { let dialog = new frappe.ui.Dialog({ diff --git a/frappe/desk/doctype/number_card/number_card.js b/frappe/desk/doctype/number_card/number_card.js index d7abe57e2a..d5a743818a 100644 --- a/frappe/desk/doctype/number_card/number_card.js +++ b/frappe/desk/doctype/number_card/number_card.js @@ -9,8 +9,65 @@ frappe.ui.form.on('Number Card', { frm.set_df_property("filters_section", "hidden", 1); frm.set_df_property("dynamic_filters_section", "hidden", 1); frm.trigger('set_options'); - frm.trigger('render_filters_table'); - frm.trigger('render_dynamic_filters_table'); + + if (!frm.doc.type) { + frm.set_value('type', 'Document Type'); + } + + if (frm.doc.type == 'Report' && frm.doc.report_name) { + frm.trigger('set_report_filters'); + } + + if (frm.doc.type == 'Custom') { + if (!frappe.boot.developer_mode) { + frm.disable_form(); + } + frm.filters = eval(frm.doc.filters_config); + frm.trigger('set_filters_description'); + frm.trigger('set_method_description'); + frm.trigger('render_filters_table'); + } + frm.trigger('create_add_to_dashboard_button'); + }, + + create_add_to_dashboard_button: function(frm) { + frm.add_custom_button('Add Card to Dashboard', () => { + const d = new frappe.ui.Dialog({ + title: __('Add to Dashboard'), + fields: [ + { + label: __('Select Dashboard'), + fieldtype: 'Link', + fieldname: 'dashboard', + options: 'Dashboard', + } + ], + primary_action: (values) => { + values.name = frm.doc.name; + frappe.xcall( + 'frappe.desk.doctype.number_card.number_card.add_card_to_dashboard', + { + args: values + } + ).then(()=> { + let dashboard_route_html = + `${values.dashboard}`; + let message = + __(`Number Card ${values.name} add to Dashboard ` + dashboard_route_html); + + frappe.msgprint(message); + }); + + d.hide(); + } + }); + + if (!frm.doc.name) { + frappe.msgprint(__('Please create Card first')); + } else { + d.show(); + } + }); }, before_save: function(frm) { @@ -21,16 +78,81 @@ frappe.ui.form.on('Number Card', { frm.set_value('filters_json', JSON.stringify(static_filters)); frm.trigger('render_filters_table'); + frm.trigger('render_dynamic_filters_table'); }, is_standard: function(frm) { - if (frappe.boot.developer_mode && frm.doc.is_standard) { - frm.trigger('render_dynamic_filters_table'); - } else { - frm.set_df_property("dynamic_filters_section", "hidden", 1); + frm.trigger('render_dynamic_filters_table'); + frm.set_df_property("dynamic_filters_section", "hidden", 1); + }, + + set_filters_description: function(frm) { + if (frm.doc.type == 'Custom') { + frm.fields_dict.filters_config.set_description(` + Set the filters here. For example: +
+
+[{
+	fieldname: "company",
+	label: __("Company"),
+	fieldtype: "Link",
+	options: "Company",
+	default: frappe.defaults.get_user_default("Company"),
+	reqd: 1
+},
+{
+	fieldname: "account",
+	label: __("Account"),
+	fieldtype: "Link",
+	options: "Account",
+	reqd: 1
+}]
+
`); } }, + set_method_description: function(frm) { + if (frm.doc.type == 'Custom') { + frm.fields_dict.method.set_description(` + Set the path to a whitelisted function that will return the number on the card in the format: +
+
+{
+	"value": value,
+	"fieldtype": "Currency"
+}
+
`); + } + }, + + type: function(frm) { + frm.trigger('set_filters_description'); + if (frm.doc.type == 'Report') { + frm.set_query('report_name', () => { + return { + filters: { + 'report_type': ['!=', 'Report Builder'] + } + }; + }); + } + + }, + + report_name: function(frm) { + frm.set_value('filters_json', '{}'); + frm.set_value('dynamic_filters_json', '{}'); + frm.set_df_property('report_field', 'options', []); + frm.trigger('set_report_filters'); + }, + + filters_config: function(frm) { + frm.filters = eval(frm.doc.filters_config); + const filter_values = frappe.report_utils.get_filter_values(frm.filters); + frm.set_value('filters_json', JSON.stringify(filter_values)); + frm.trigger('render_filters_table'); + }, + document_type: function(frm) { frm.set_query('document_type', function() { return { @@ -46,6 +168,10 @@ frappe.ui.form.on('Number Card', { }, set_options: function(frm) { + if (frm.doc.type !== 'Document Type') { + return; + } + let aggregate_based_on_fields = []; const doctype = frm.doc.document_type; @@ -64,72 +190,60 @@ frappe.ui.form.on('Number Card', { frm.set_df_property('aggregate_function_based_on', 'options', aggregate_based_on_fields); }); + frm.trigger('render_filters_table'); + frm.trigger('render_dynamic_filters_table'); } }, + set_report_filters: function(frm) { + const report_name = frm.doc.report_name; + if (report_name) { + frappe.report_utils.get_report_filters(report_name).then(filters => { + if (filters) { + frm.filters = filters; + const filter_values = frappe.report_utils.get_filter_values(filters); + if (frm.doc.filters_json.length <= 2) { + frm.set_value('filters_json', JSON.stringify(filter_values)); + } + } + frm.trigger('render_filters_table'); + frm.trigger('set_report_field_options'); + frm.trigger('render_dynamic_filters_table'); + }); + } + }, + + set_report_field_options: function(frm) { + let filters = frm.doc.filters_json.length > 2 ? JSON.parse(frm.doc.filters_json) : null; + if (frm.doc.dynamic_filters_json.length > 2) { + filters = {...filters, ...JSON.parse(frm.doc.dynamic_filters_json)}; + } + frappe.xcall( + 'frappe.desk.query_report.run', + { + report_name: frm.doc.report_name, + filters: filters, + ignore_prepared_report: 1 + } + ).then(data => { + if (data.result.length) { + frm.field_options = frappe.report_utils.get_field_options_from_report(data.columns, data); + frm.set_df_property('report_field', 'options', frm.field_options.numeric_fields); + if (!frm.field_options.numeric_fields.length) { + frappe.msgprint(__(`Report has no numeric fields, please change the Report Name`)); + } + } else { + frappe.msgprint(__('Report has no data, please modify the filters or change the Report Name')); + } + }); + }, + render_filters_table: function(frm) { frm.set_df_property("filters_section", "hidden", 0); + let is_document_type = frm.doc.type == 'Document Type'; + let is_dynamic_filter = f => ['Date', 'DateRange'].includes(f.fieldtype) && f.default; let wrapper = $(frm.get_field('filters_json').wrapper).empty(); - let table = $(` - - - - - - - - -
${__('Filter')}${__('Condition')}${__('Value')}
`).appendTo(wrapper); - - frm.filters = JSON.parse(frm.doc.filters_json || '[]'); - - set_filters_in_table(frm.filters, table); - - table.on('click', () => { - let dialog = new frappe.ui.Dialog({ - title: __('Set Filters'), - fields: [{ - fieldtype: 'HTML', - fieldname: 'filter_area', - }], - primary_action: function() { - let values = this.get_values(); - if (values) { - this.hide(); - frm.filters = frm.filter_group.get_filters(); - frm.set_value('filters_json', JSON.stringify(frm.filters)); - set_filters_in_table(frm.filters, table); - frm.trigger('render_dynamic_filters_table'); - } - }, - primary_action_label: "Set" - }); - - frappe.dashboards.filters_dialog = dialog; - - frm.filter_group = new frappe.ui.FilterGroup({ - parent: dialog.get_field('filter_area').$wrapper, - doctype: frm.doc.document_type, - on_change: () => {}, - }); - - frm.filter_group.add_filters_to_filter_group(frm.filters); - - dialog.show(); - dialog.set_values(frm.filters); - }); - - }, - - render_dynamic_filters_table: function(frm) { - if (!frappe.boot.developer_mode || !frm.doc.is_standard) { - return; - } - let wrapper = $(frm.get_field('dynamic_filters_json').wrapper).empty(); - - frm.set_df_property("dynamic_filters_section", "hidden", 0); - let table = $(` @@ -140,92 +254,211 @@ frappe.ui.form.on('Number Card', {
`).appendTo(wrapper); - - frm.dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || '[]'); - - set_filters_in_table(frm.dynamic_filters, table); + $(`

${__("Click table to edit")}

`).appendTo(wrapper); let filters = JSON.parse(frm.doc.filters_json || '[]'); - let fields = [ - { - fieldtype: 'HTML', - fieldname: 'description', - options: - `
-

Set dynamic filter values in JavaScript for the required fields here. -

-

Ex: - frappe.defaults.get_user_default("Company") -

-
` - } - ]; + let filters_set = false; - if (frm.dynamic_filters.length) { - filters = [...filters, ...frm.dynamic_filters]; + // Set dynamic filters for reports + if (frm.doc.type == 'Report') { + let set_filters = false; + frm.filters.forEach(f => { + if (is_dynamic_filter(f)) { + filters[f.fieldname] = f.default; + set_filters = true; + } + }); + set_filters && frm.set_value('filters_json', JSON.stringify(filters)); } - filters.forEach(f => { - for (let field of fields) { - if (field.fieldname == f[0] + ':' + f[1]) { - return; + let fields; + if (is_document_type) { + fields = [ + { + fieldtype: 'HTML', + fieldname: 'filter_area', } - } - if (f[2] == '=') { - fields.push({ - label: `${f[1]} (${f[0]})`, - fieldname: f[0] + ':' + f[1], - fieldtype: 'Data', + ]; + + if (filters.length) { + filters.forEach(filter => { + const filter_row = + $(` + ${filter[1]} + ${filter[2] || ""} + ${filter[3]} + `); + + table.find('tbody').append(filter_row); }); + filters_set = true; } - }); + } else if (frm.filters.length) { + fields = frm.filters.filter(f => f.fieldname); + fields.map(f => { + if (filters[f.fieldname]) { + let condition = '='; + const filter_row = + $(` + ${f.label} + ${condition} + ${filters[f.fieldname] || ""} + `); + table.find('tbody').append(filter_row); + if (!filters_set) filters_set = true; + } + }); + } + + if (!filters_set) { + const filter_row = $(` + ${__("Click to Set Filters")}`); + table.find('tbody').append(filter_row); + } table.on('click', () => { let dialog = new frappe.ui.Dialog({ title: __('Set Filters'), - fields: fields, - primary_action: () => { - let values = dialog.get_values(); + fields: fields.filter(f => !is_dynamic_filter(f)), + primary_action: function() { + let values = this.get_values(); if (values) { - dialog.hide(); - let dynamic_filters = []; - for (let key of Object.keys(values)) { - let [doctype, fieldname] = key.split(':'); - dynamic_filters.push([doctype, fieldname, '=', values[key]]); + this.hide(); + if (is_document_type) { + let filters = frm.filter_group.get_filters(); + frm.set_value('filters_json', JSON.stringify(filters)); + } else { + frm.set_value('filters_json', JSON.stringify(values)); } - - frm.set_value('dynamic_filters_json', JSON.stringify(dynamic_filters)); - frm.dynamic_filters = dynamic_filters; - set_filters_in_table(frm.dynamic_filters, table); + frm.trigger('render_filters_table'); } }, primary_action_label: "Set" }); + if (is_document_type) { + frm.filter_group = new frappe.ui.FilterGroup({ + parent: dialog.get_field('filter_area').$wrapper, + doctype: frm.doc.document_type, + on_change: () => {}, + }); + filters && frm.filter_group.add_filters_to_filter_group(filters); + } + dialog.show(); + + if (frm.doc.type == 'Report') { + //Set query report object so that it can be used while fetching filter values in the report + frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list}); + frappe.query_reports[frm.doc.report_name] + && frappe.query_reports[frm.doc.report_name].onload + && frappe.query_reports[frm.doc.report_name].onload(frappe.query_report); + } + dialog.set_values(filters); }); }, -}); + render_dynamic_filters_table(frm) { + if (!frappe.boot.developer_mode || !frm.doc.is_standard || frm.doc.type == 'Custom') { + return; + } -function set_filters_in_table(filters, table) { - if (!filters.length) { - const filter_row = $(` - ${__("Click to Set Filters")}`); - table.find('tbody').html(filter_row); - } else { - let filter_rows = ''; - filters.forEach(filter => { - filter_rows += - ` - ${filter[1]} - ${filter[2] || ""} - ${filter[3]} - `; + frm.set_df_property("dynamic_filters_section", "hidden", 0); + let is_document_type = frm.doc.type == 'Document Type'; + + let wrapper = $(frm.get_field('dynamic_filters_json').wrapper).empty(); + + frm.dynamic_filter_table = $(` + + + + + + + + +
${__('Filter')}${__('Condition')}${__('Value')}
`).appendTo(wrapper); + + frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2 + ? JSON.parse(frm.doc.dynamic_filters_json) + : null; + + frm.trigger('set_dynamic_filters_in_table'); + + let filters = JSON.parse(frm.doc.filters_json || '[]'); + + let fields = frappe.dashboard_utils.get_fields_for_dynamic_filter_dialog( + is_document_type, filters, frm.dynamic_filters + ); + + frm.dynamic_filter_table.on('click', () => { + let dialog = new frappe.ui.Dialog({ + title: __('Set Dynamic Filters'), + fields: fields, + primary_action: () => { + let values = dialog.get_values(); + dialog.hide(); + let dynamic_filters = []; + for (let key of Object.keys(values)) { + if (is_document_type) { + let [doctype, fieldname] = key.split(':'); + dynamic_filters.push([doctype, fieldname, '=', values[key]]); + } + } + + if (is_document_type) { + frm.set_value('dynamic_filters_json', JSON.stringify(dynamic_filters)); + } else { + frm.set_value('dynamic_filters_json', JSON.stringify(values)); + } + frm.trigger('set_dynamic_filters_in_table'); + }, + primary_action_label: "Set" + }); + + dialog.show(); + dialog.set_values(frm.dynamic_filters); }); - table.find('tbody').html(filter_rows); + }, + + set_dynamic_filters_in_table: function(frm) { + frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2 + ? JSON.parse(frm.doc.dynamic_filters_json) + : null; + + if (!frm.dynamic_filters) { + const filter_row = $(` + ${__("Click to Set Dynamic Filters")}`); + frm.dynamic_filter_table.find('tbody').html(filter_row); + } else { + let filter_rows = ''; + if ($.isArray(frm.dynamic_filters)) { + frm.dynamic_filters.forEach(filter => { + filter_rows += + ` + ${filter[1]} + ${filter[2] || ""} + ${filter[3]} + `; + }); + } else { + let condition = '='; + for (let [key, val] of Object.entries(frm.dynamic_filters)) { + filter_rows += + ` + ${key} + ${condition} + ${val || ""} + ` + ; + } + } + + frm.dynamic_filter_table.find('tbody').html(filter_rows); + } } -} + +}); diff --git a/frappe/desk/doctype/number_card/number_card.json b/frappe/desk/doctype/number_card/number_card.json index 41362a8982..333885a7f9 100644 --- a/frappe/desk/doctype/number_card/number_card.json +++ b/frappe/desk/doctype/number_card/number_card.json @@ -9,11 +9,18 @@ "is_standard", "module", "label", + "type", + "report_name", + "method", "function", "aggregate_function_based_on", "column_break_2", "document_type", + "report_field", + "report_function", "is_public", + "custom_configuration_section", + "filters_config", "stats_section", "show_percentage_stats", "stats_time_interval", @@ -26,20 +33,21 @@ ], "fields": [ { + "depends_on": "eval: doc.type == 'Document Type'", "fieldname": "document_type", "fieldtype": "Link", "in_list_view": 1, "label": "Document Type", - "options": "DocType", - "reqd": 1 + "mandatory_depends_on": "eval: doc.type == 'Document Type'", + "options": "DocType" }, { - "depends_on": "eval: doc.document_type", + "depends_on": "eval: doc.type == 'Document Type'", "fieldname": "function", "fieldtype": "Select", "label": "Function", - "options": "Count\nSum\nAverage\nMinimum\nMaximum", - "reqd": 1 + "mandatory_depends_on": "eval: doc.type == 'Document Type'", + "options": "Count\nSum\nAverage\nMinimum\nMaximum" }, { "depends_on": "eval: doc.function !== 'Count'", @@ -98,6 +106,7 @@ "options": "Daily\nWeekly\nMonthly\nYearly" }, { + "depends_on": "eval: doc.type == 'Document Type'", "fieldname": "stats_section", "fieldtype": "Section Break", "label": "Stats" @@ -114,34 +123,74 @@ "fieldtype": "Link", "label": "Module", "mandatory_depends_on": "eval: doc.is_standard", - "options": "Module Def", - "show_days": 1, - "show_seconds": 1 + "options": "Module Def" }, { "fieldname": "dynamic_filters_json", "fieldtype": "Code", "label": "Dynamic Filters JSON", - "options": "JSON", - "show_days": 1, - "show_seconds": 1 + "options": "JSON" }, { "fieldname": "section_break_16", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "dynamic_filters_section", "fieldtype": "Section Break", - "label": "Dynamic Filters Section", - "show_days": 1, - "show_seconds": 1 + "label": "Dynamic Filters Section" + }, + { + "fieldname": "type", + "fieldtype": "Select", + "label": "Type", + "options": "Document Type\nReport\nCustom" + }, + { + "depends_on": "eval: doc.type == 'Report'", + "fieldname": "report_name", + "fieldtype": "Link", + "label": "Report Name", + "mandatory_depends_on": "eval: doc.type == 'Report'", + "options": "Report" + }, + { + "depends_on": "eval: doc.type == 'Report'", + "fieldname": "report_field", + "fieldtype": "Select", + "label": "Field", + "mandatory_depends_on": "eval: doc.type == 'Report'" + }, + { + "depends_on": "eval: doc.type == 'Custom'", + "fieldname": "method", + "fieldtype": "Data", + "label": "Method", + "mandatory_depends_on": "eval: doc.type == 'Custom'" + }, + { + "depends_on": "eval: doc.type == 'Custom'", + "fieldname": "custom_configuration_section", + "fieldtype": "Section Break", + "label": "Custom Configuration" + }, + { + "fieldname": "filters_config", + "fieldtype": "Code", + "label": "Filters Configuration", + "options": "JSON" + }, + { + "depends_on": "eval: doc.type == 'Report'", + "fieldname": "report_function", + "fieldtype": "Select", + "label": "Function", + "mandatory_depends_on": "eval: doc.type == 'Report'", + "options": "Sum\nAverage\nMinimum\nMaximum" } ], "links": [], - "modified": "2020-07-10 17:55:35.873222", + "modified": "2020-07-18 17:08:22.882538", "modified_by": "Administrator", "module": "Desk", "name": "Number Card", diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index 2f5cfb561c..5b52b60474 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -52,7 +52,7 @@ def has_permission(doc, ptype, user): return False @frappe.whitelist() -def get_result(doc, to_date=None): +def get_result(doc, filters, to_date=None): doc = frappe.parse_json(doc) fields = [] sql_function_map = { @@ -70,13 +70,13 @@ def get_result(doc, to_date=None): else: fields = ['{function}({based_on}) as result'.format(function=function, based_on=doc.aggregate_function_based_on)] - filters = frappe.parse_json(doc.filters_json) + filters = frappe.parse_json(filters) if not filters: filters = [] if to_date: - filters.append([doc.document_type, 'creation', '<', to_date, False]) + filters.append([doc.document_type, 'creation', '<', to_date]) res = frappe.db.get_list(doc.document_type, fields=fields, filters=filters) number = res[0]['result'] if res else 0 @@ -84,7 +84,7 @@ def get_result(doc, to_date=None): return cint(number) @frappe.whitelist() -def get_percentage_difference(doc, result): +def get_percentage_difference(doc, filters, result): doc = frappe.parse_json(doc) result = frappe.parse_json(result) @@ -93,13 +93,13 @@ def get_percentage_difference(doc, result): if not doc.get('show_percentage_stats'): return - previous_result = calculate_previous_result(doc) + previous_result = calculate_previous_result(doc, filters) difference = (result - previous_result)/100.0 return difference -def calculate_previous_result(doc): +def calculate_previous_result(doc, filters): from frappe.utils import add_to_date current_date = frappe.utils.now() @@ -112,7 +112,7 @@ def calculate_previous_result(doc): else: previous_date = add_to_date(current_date, years=-1) - number = get_result(doc, previous_date) + number = get_result(doc, filters, previous_date) return number @frappe.whitelist() @@ -155,3 +155,22 @@ def get_cards_for_user(doctype, txt, searchfield, start, page_len, filters): search_conditions=search_conditions, conditions=conditions ), values) + +@frappe.whitelist() +def create_report_number_card(args): + card = create_number_card(args) + args = frappe.parse_json(args) + args.name = card.name + if args.dashboard: + add_card_to_dashboard(frappe.as_json(args)) + +@frappe.whitelist() +def add_card_to_dashboard(args): + args = frappe.parse_json(args) + + dashboard = frappe.get_doc('Dashboard', args.dashboard) + dashboard_link = frappe.new_doc('Number Card Link') + dashboard_link.card = args.name + + dashboard.append('cards', dashboard_link) + dashboard.save() \ No newline at end of file diff --git a/frappe/model/document.py b/frappe/model/document.py index ea693167f8..69a781d6d1 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -403,9 +403,16 @@ class Document(BaseDocument): def set_new_name(self, force=False, set_name=None, set_child_names=True): """Calls `frappe.naming.set_new_name` for parent and child docs.""" + if self.flags.name_set and not force: return + # If autoname has set as Prompt (name) + if self.get("__newname"): + self.name = self.get("__newname") + self.flags.name_set = True + return + if set_name: self.name = set_name else: diff --git a/frappe/patches.txt b/frappe/patches.txt index 1108f1fb1b..f8c767f5a3 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -273,6 +273,7 @@ execute:frappe.delete_doc_if_exists('DocType', 'GSuite Templates') execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Account') execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings') frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats +frappe.patches.v12_0.remove_example_email_thread_notify execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders() frappe.patches.v12_0.set_correct_url_in_files frappe.patches.v13_0.website_theme_custom_scss @@ -293,4 +294,4 @@ execute:frappe.delete_doc("DocType", "Onboarding Slide Help Link") frappe.patches.v13_0.update_date_filters_in_user_settings frappe.patches.v13_0.update_duration_options frappe.patches.v13_0.replace_old_data_import # 2020-06-24 -frappe.patches.v13_0.create_custom_dashboards_cards_and_charts \ No newline at end of file +frappe.patches.v13_0.create_custom_dashboards_cards_and_charts diff --git a/frappe/patches/v12_0/remove_example_email_thread_notify.py b/frappe/patches/v12_0/remove_example_email_thread_notify.py new file mode 100644 index 0000000000..94959b6077 --- /dev/null +++ b/frappe/patches/v12_0/remove_example_email_thread_notify.py @@ -0,0 +1,8 @@ +import frappe + + +def execute(): + # remove all example.com email user accounts from notifications + frappe.db.sql("""UPDATE `tabUser` + SET thread_notify=0, send_me_a_copy=0 + WHERE email like '%@example.com'""") diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index 68444c8a3b..2da7b8f236 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -240,13 +240,8 @@ frappe.ui.form.QuickEntryForm = Class.extend({ var me = this; var data = this.dialog.get_values(true); $.each(data, function(key, value) { - if(key==='__newname') { - me.dialog.doc.name = value; - } - else { - if(!is_null(value)) { - me.dialog.doc[key] = value; - } + if (!is_null(value)) { + me.dialog.doc[key] = value; } }); return this.dialog.doc; @@ -282,7 +277,7 @@ frappe.ui.form.QuickEntryForm = Class.extend({ field.doctype = me.doc.doctype; field.docname = me.doc.name; - if(!is_null(me.doc[fieldname])) { + if (!is_null(me.doc[fieldname])) { field.set_input(me.doc[fieldname]); } }); diff --git a/frappe/public/js/frappe/utils/dashboard_utils.js b/frappe/public/js/frappe/utils/dashboard_utils.js index f737c6ad12..8618f6dd59 100644 --- a/frappe/public/js/frappe/utils/dashboard_utils.js +++ b/frappe/public/js/frappe/utils/dashboard_utils.js @@ -119,6 +119,86 @@ frappe.dashboard_utils = { } return static_filters; + }, + + get_fields_for_dynamic_filter_dialog(is_document_type, filters, dynamic_filters) { + let fields = [ + { + fieldtype: 'HTML', + fieldname: 'description', + options: + `
+

Set dynamic filter values in JavaScript for the required fields here. +

+

Ex: + frappe.defaults.get_user_default("Company") +

+
` + } + ]; + + if (is_document_type) { + if (dynamic_filters) { + filters = [...filters, ...dynamic_filters]; + } + filters.forEach(f => { + for (let field of fields) { + if (field.fieldname == f[0] + ':' + f[1]) { + return; + } + } + if (f[2] == '=') { + fields.push({ + label: `${f[1]} (${f[0]})`, + fieldname: f[0] + ':' + f[1], + fieldtype: 'Data', + }); + } + }); + } else { + filters = {...dynamic_filters, ...filters}; + for (let key of Object.keys(filters)) { + fields.push({ + label: key, + fieldname: key, + fieldtype: 'Data', + }); + } + } + + return fields; + }, + + get_all_filters(doc) { + let filters = JSON.parse(doc.filters_json || "null"); + let dynamic_filters = JSON.parse(doc.dynamic_filters_json || "null"); + + if (!dynamic_filters) { + return filters; + } + + if ($.isArray(dynamic_filters)) { + dynamic_filters.forEach(f => { + try { + f[3] = eval(f[3]); + } catch (e) { + frappe.throw(__(`Invalid expression set in filter ${f[1]} (${f[0]})`)); + } + }); + filters = [...filters, ...dynamic_filters]; + } else { + for (let key of Object.keys(dynamic_filters)) { + try { + const val = eval(dynamic_filters[key]); + dynamic_filters[key] = val; + } catch (e) { + frappe.throw(__(`Invalid expression set in filter ${key}`)); + } + } + Object.assign(filters, dynamic_filters); + } + + return filters; } }; \ No newline at end of file diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index f82956adac..40ebd1c497 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -128,10 +128,17 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { () => this.setup_progress_bar(), () => this.setup_page_head(), () => this.refresh_report(), - () => this.add_chart_buttons_to_toolbar(true) + () => this.add_chart_buttons_to_toolbar(true), + () => this.add_card_button_to_toolbar(true), ]); } + add_card_button_to_toolbar() { + this.page.add_inner_button(__("Create Card"), () => { + this.add_card_to_dashboard(); + }); + } + add_chart_buttons_to_toolbar(show) { if (show) { this.page.add_inner_button(__("Set Chart"), () => { @@ -148,6 +155,62 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } } + add_card_to_dashboard() { + let field_options = frappe.report_utils.get_field_options_from_report(this.columns, this.raw_data); + const dialog = new frappe.ui.Dialog({ + title: __('Create Card'), + fields: [ + { + fieldname: 'report_field', + label: __('Field'), + fieldtype: 'Select', + options: field_options.numeric_fields, + }, + { + fieldname: 'cb_1', + fieldtype: 'Column Break' + }, + { + fieldname: 'report_function', + label: __('Function'), + options: ['Sum', 'Average', 'Minimum', 'Maximum'], + fieldtype: 'Select' + }, + { + fieldname: 'sb_1', + label: __('Add to Dashboard'), + fieldtype: 'Section Break' + }, + { + fieldname: 'dashboard', + label: __('Choose Dashboard'), + fieldtype: 'Link', + options: 'Dashboard', + }, + { + fieldname: 'cb_2', + fieldtype: 'Column Break' + }, + { + fieldname: 'label', + label: __('Card Label'), + fieldtype: 'Data', + } + ], + primary_action_label: __('Add'), + primary_action: (values) => { + if (!values.label) { + values.label = `${values.report_function} of ${toTitle(values.report_field)}`; + } + this.create_number_card(values, values.dashboard, values.label); + dialog.hide(); + } + }); + + dialog.show(); + + } + add_chart_to_dashboard() { if (this.chart_fields || this.chart_options) { const dialog = new frappe.ui.Dialog({ @@ -182,6 +245,24 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } } + create_number_card(values, dashboard_name, card_name) { + let args = { + 'dashboard': dashboard_name || null, + 'type': 'Report', + 'report_name': this.report_name, + 'filters_json': JSON.stringify(this.get_filter_values()), + }; + Object.assign(args, values); + + this.add_to_dashboard( + 'frappe.desk.doctype.number_card.number_card.create_report_number_card', + args, + dashboard_name, + card_name, + 'Number Card' + ); + } + create_dashboard_chart(chart_args, dashboard_name, chart_name) { let args = { 'dashboard': dashboard_name || null, @@ -224,19 +305,29 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { ); } - frappe.xcall( + this.add_to_dashboard( 'frappe.desk.doctype.dashboard_chart.dashboard_chart.create_report_chart', + args, + dashboard_name, + chart_name, + 'Dashboard Chart' + ); + } + + add_to_dashboard(method, args, dashboard_name, name, doctype) { + frappe.xcall( + method, {args: args} - ).then( () => { + ).then(() => { let message; if (dashboard_name) { let dashboard_route_html = `${dashboard_name}`; - message = __(`New Dashboard Chart ${chart_name} added to Dashboard ` + dashboard_route_html); + message = __(`New {0} {1} added to Dashboard ` + dashboard_route_html, [doctype, name]); } else { - message = __(`New chart ${chart_name} created`); + message = __(`New {0} {1} created`, [doctype, name]); } - frappe.msgprint(message, __('New Chart Created')); + frappe.msgprint(message, __(`New {0} Created`, [doctype])); }); } @@ -518,6 +609,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } this.render_datatable(); this.add_chart_buttons_to_toolbar(true); + this.add_card_button_to_toolbar(); } else { this.data = []; this.toggle_nothing_to_show(true); @@ -700,7 +792,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { open_create_chart_dialog() { const me = this; - let field_options = frappe.report_utils.get_possible_chart_options(this.columns, this.raw_data); + let field_options = frappe.report_utils.get_field_options_from_report(this.columns, this.raw_data); function set_chart_values(values) { values.y_fields = []; diff --git a/frappe/public/js/frappe/views/reports/report_utils.js b/frappe/public/js/frappe/views/reports/report_utils.js index 7b1205482f..158dbd653b 100644 --- a/frappe/public/js/frappe/views/reports/report_utils.js +++ b/frappe/public/js/frappe/views/reports/report_utils.js @@ -41,7 +41,7 @@ frappe.report_utils = { } }, - get_possible_chart_options: function(columns, data) { + get_field_options_from_report: function(columns, data) { const rows = data.result.filter(value => Object.keys(value).length); const first_row = Array.isArray(rows[0]) ? rows[0] : columns.map(col => rows[0][col.fieldname]); @@ -138,4 +138,14 @@ frappe.report_utils = { return filter_values; }, + get_result_of_fn(fn, values) { + const get_result = { + 'Minimum': values => values.reduce((min, val) => Math.min(min, val), values[0]), + 'Maximum': values => values.reduce((min, val) => Math.max(min, val), values[0]), + 'Average': values => values.reduce((a, b) => a + b, 0) / values.length, + 'Sum': values => values.reduce((a, b) => a + b, 0) + }; + return get_result[fn](values); + }, + }; diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index e31df3825b..674f53670e 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -639,7 +639,7 @@ export default class ChartWidget extends Widget { set_chart_filters() { let user_saved_filters = this.chart_settings.filters || null; - let chart_saved_filters = this.get_all_chart_filters(); + let chart_saved_filters = frappe.dashboard_utils.get_all_filters(this.chart_doc); if (this.chart_doc.chart_type == 'Report') { return frappe.dashboard_utils @@ -655,38 +655,6 @@ export default class ChartWidget extends Widget { } } - get_all_chart_filters() { - let filters = JSON.parse(this.chart_doc.filters_json || "null"); - let dynamic_filters = JSON.parse(this.chart_doc.dynamic_filters_json || "null"); - - if (!dynamic_filters) { - return filters; - } - - if ($.isArray(dynamic_filters)) { - dynamic_filters.forEach(f => { - try { - f[3] = eval(f[3]); - } catch (e) { - frappe.throw(__(`Invalid expression set in filter ${f[1]} (${f[0]})`)); - } - }); - filters = [...filters, ...dynamic_filters]; - } else { - for (let key of Object.keys(dynamic_filters)) { - try { - const val = eval(dynamic_filters[key]); - dynamic_filters[key] = val; - } catch (e) { - frappe.throw(__(`Invalid expression set in filter ${key}`)); - } - } - Object.assign(filters, dynamic_filters); - } - - return filters; - } - update_default_date_filters(report_filters, chart_filters) { report_filters.map(f => { if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) { diff --git a/frappe/public/js/frappe/widgets/number_card_widget.js b/frappe/public/js/frappe/widgets/number_card_widget.js index 77cb8a59c2..220394919c 100644 --- a/frappe/public/js/frappe/widgets/number_card_widget.js +++ b/frappe/public/js/frappe/widgets/number_card_widget.js @@ -1,5 +1,5 @@ import Widget from "./base_widget.js"; -import { go_to_list_with_filters, shorten_number } from "./utils"; +import { generate_route, shorten_number } from "./utils"; export default class NumberCardWidget extends Widget { constructor(opts) { @@ -59,20 +59,39 @@ export default class NumberCardWidget extends Widget { } ).then(doc => { this.name = doc.name; - this.card_doc.stats_time_interval = doc.stats_time_interval; - this.card_doc.name = this.name; + this.card_doc = doc; this.widget.attr('data-widget-name', this.name); }); } set_events() { $(this.body).click(() => { - if (this.in_customize_mode) return; - let filters = JSON.parse(this.card_doc.filters_json); - go_to_list_with_filters(this.card_doc.document_type, filters); + if (this.in_customize_mode || this.card_doc.type == 'Custom') return; + this.set_route(); }); } + set_route() { + const is_document_type = this.card_doc.type !== 'Report'; + const name = is_document_type ? this.card_doc.document_type : this.card_doc.report_name; + const route = generate_route({ + name: name, + type: is_document_type ? 'doctype' : 'report', + is_query_report: !is_document_type, + }); + + if (is_document_type) { + const filters = JSON.parse(this.card_doc.filters_json); + frappe.route_options = filters.reduce((acc, filter) => { + return Object.assign(acc, { + [`${filter[0]}.${filter[1]}`]: [filter[2], filter[3]] + }); + }, {}); + } + + frappe.set_route(route); + } + set_doc_args() { this.card_doc = Object.assign({}, { document_type: this.document_type, @@ -84,11 +103,53 @@ export default class NumberCardWidget extends Widget { }); } + get_settings(type) { + this.filters = this.get_filters(); + const settings_map = { + 'Custom': { + method: this.card_doc.method, + args: { + filters: this.filters + }, + get_number: res => this.get_number_for_custom_card(res), + }, + 'Report': { + method: 'frappe.desk.query_report.run', + args: { + report_name: this.card_doc.report_name, + filters: this.filters, + ignore_prepared_report: 1 + }, + get_number: res => this.get_number_for_report_card(res), + }, + 'Document Type': { + method: 'frappe.desk.doctype.number_card.number_card.get_result', + args: { + doc: this.card_doc, + filters: this.filters, + }, + get_number: res => this.get_number_for_doctype_card(res), + } + }; + return settings_map[type]; + } + + get_filters() { + const filters = frappe.dashboard_utils.get_all_filters(this.card_doc); + return filters; + } + render_card() { this.prepare_actions(); this.set_title(); this.set_loading_state(); + if (!this.card_doc.type) { + this.card_doc.type = 'Document Type'; + } + + this.settings = this.get_settings(this.card_doc.type); + frappe.run_serially([ () => this.render_number(), () => this.render_stats(), @@ -102,43 +163,69 @@ export default class NumberCardWidget extends Widget { } get_number() { - return frappe.xcall('frappe.desk.doctype.number_card.number_card.get_result', { - doc: this.card_doc - }).then(res => { - this.number = res; - if (this.card_doc.function !== 'Count') { - return frappe.model.with_doctype(this.card_doc.document_type, () => { - this.get_formatted_number(); - }); - } else { - this.number_html = res; - } + return frappe.xcall(this.settings.method, this.settings.args).then(res => { + this.settings.get_number(res); }); } - get_formatted_number() { - const based_on_df = - frappe.meta.get_docfield(this.card_doc.document_type, this.card_doc.aggregate_function_based_on); + get_number_for_custom_card(res) { + if (typeof res === 'object') { + this.number = res.value; + this.get_formatted_number(res); + } else { + this.formatted_number = res; + } + } + + get_number_for_doctype_card(res) { + this.number = res; + if (this.card_doc.function !== 'Count') { + return frappe.model.with_doctype(this.card_doc.document_type, () => { + const based_on_df = + frappe.meta.get_docfield(this.card_doc.document_type, this.card_doc.aggregate_function_based_on); + this.get_formatted_number(based_on_df); + }); + } else { + this.formatted_number = res; + } + } + + get_number_for_report_card(res) { + const field = this.card_doc.report_field; + const vals = res.result.reduce((acc, col) => { + col[field] && acc.push(col[field]); + return acc; + }, []); + const col = res.columns.find(col => col.fieldname == field); + this.number = frappe.report_utils.get_result_of_fn(this.card_doc.report_function, vals); + this.get_formatted_number(col); + } + + get_formatted_number(df) { const default_country = frappe.sys_defaults.country; const shortened_number = shorten_number(this.number, default_country); let number_parts = shortened_number.split(' '); const symbol = number_parts[1] || ''; - const formatted_number = $(frappe.format(number_parts[0], based_on_df)).text(); + const formatted_number = $(frappe.format(number_parts[0], df)).text(); - this.number_html = formatted_number + ' ' + symbol; + this.formatted_number = formatted_number + ' ' + symbol; } render_number() { return this.get_number().then(() => { $(this.body).html(`
-
${this.number_html}
+
${this.formatted_number}
`); }); } render_stats() { - let caret_html =''; + if (this.card_doc.type !== 'Document Type') { + return; + } + + let caret_html = ''; let color_class = ''; return this.get_percentage_stats().then(() => { @@ -177,6 +264,7 @@ export default class NumberCardWidget extends Widget { get_percentage_stats() { return frappe.xcall('frappe.desk.doctype.number_card.number_card.get_percentage_difference', { doc: this.card_doc, + filters: this.filters, result: this.number }).then(res => { if (res !== undefined) { diff --git a/frappe/public/js/frappe/widgets/utils.js b/frappe/public/js/frappe/widgets/utils.js index dff4db807e..22b3167977 100644 --- a/frappe/public/js/frappe/widgets/utils.js +++ b/frappe/public/js/frappe/widgets/utils.js @@ -117,16 +117,6 @@ const build_summary_item = (summary) => { `); }; -function go_to_list_with_filters(doctype, filters) { - const route = `List/${doctype}/List`; - frappe.set_route(route).then(()=> { - let list_view = frappe.views.list_view[route]; - let filter_area = list_view.filter_area; - filter_area.clear(); - filter_area.filter_list.add_filters_to_filter_group(filters); - }); -} - function shorten_number(number, country) { country = (country == 'India') ? country : ''; const number_system = get_number_system(country); @@ -167,4 +157,4 @@ function get_number_system(country) { return number_system_map[country]; } -export { generate_route, generate_grid, build_summary_item, go_to_list_with_filters, shorten_number }; \ No newline at end of file +export { generate_route, generate_grid, build_summary_item, shorten_number }; \ No newline at end of file diff --git a/frappe/utils/install.py b/frappe/utils/install.py index e5bf122d81..bb384fb300 100644 --- a/frappe/utils/install.py +++ b/frappe/utils/install.py @@ -50,11 +50,13 @@ def install_basic_docs(): install_docs = [ {'doctype':'User', 'name':'Administrator', 'first_name':'Administrator', 'email':'admin@example.com', 'enabled':1, "is_admin": 1, - 'roles': [{'role': 'Administrator'}] + 'roles': [{'role': 'Administrator'}], + 'thread_notify': 0, 'send_me_a_copy': 0 }, {'doctype':'User', 'name':'Guest', 'first_name':'Guest', 'email':'guest@example.com', 'enabled':1, "is_guest": 1, - 'roles': [{'role': 'Guest'}] + 'roles': [{'role': 'Guest'}], + 'thread_notify': 0, 'send_me_a_copy': 0 }, {'doctype': "Role", "role_name": "Report Manager"}, {'doctype': "Role", "role_name": "Translator"}, diff --git a/frappe/website/utils.py b/frappe/website/utils.py index f5e35976eb..dc981fe30a 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -9,7 +9,7 @@ import frappe from six import iteritems from past.builtins import cmp -from frappe.utils import markdown +from frappe.utils import md_to_html def delete_page_cache(path): @@ -357,7 +357,7 @@ def get_html_content_based_on_type(doc, fieldname, content_type): content = doc.get(fieldname) if content_type == 'Markdown': - content = markdown(doc.get(fieldname + '_md')) + content = md_to_html(doc.get(fieldname + '_md')) elif content_type == 'HTML': content = doc.get(fieldname + '_html') diff --git a/frappe/workflow/doctype/workflow/workflow.json b/frappe/workflow/doctype/workflow/workflow.json index 28bc186bc8..e22d21b5d3 100644 --- a/frappe/workflow/doctype/workflow/workflow.json +++ b/frappe/workflow/doctype/workflow/workflow.json @@ -1,389 +1,123 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, + "actions": [], "allow_rename": 1, "autoname": "field:workflow_name", - "beta": 0, "creation": "2012-12-28 10:49:55", - "custom": 0, "description": "Defines workflow states and rules for a document.", - "docstatus": 0, "doctype": "DocType", "document_type": "Document", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "workflow_name", + "document_type", + "is_active", + "override_status", + "send_email_alert", + "states_head", + "states", + "transition_rules", + "transitions", + "workflow_state_field" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "workflow_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Workflow Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "DocType on which this Workflow is applicable.", "fieldname": "document_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Document Type", - "length": 0, - "no_copy": 0, "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "description": "If checked, all other workflows become inactive.", "fieldname": "is_active", "fieldtype": "Check", - "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": "Is Active", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 + "label": "Is Active" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "description": "If Checked workflow status will not override status in list view", "fieldname": "override_status", "fieldtype": "Check", - "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": "Don't Override Status", - "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 + "label": "Don't Override Status" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "description": "Emails will be sent with next possible workflow actions", "fieldname": "send_email_alert", "fieldtype": "Check", - "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": "Send Email Alert", - "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 + "label": "Send Email Alert" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Different \"States\" this document can exist in. Like \"Open\", \"Pending Approval\" etc.", "fieldname": "states_head", "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": "States", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 + "label": "States" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "All possible Workflow States and roles of the workflow. Docstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"", "fieldname": "states", "fieldtype": "Table", - "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": "Document States", - "length": 0, - "no_copy": 0, "options": "Workflow Document State", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Rules for how states are transitions, like next state and which role is allowed to change state etc.", "fieldname": "transition_rules", "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": "Transition Rules", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 + "label": "Transition Rules" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Rules defining transition of state in the workflow.", "fieldname": "transitions", "fieldtype": "Table", - "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": "Transitions", - "length": 0, - "no_copy": 0, "options": "Workflow Transition", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "workflow_state", "description": "Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)", "fieldname": "workflow_state_field", "fieldtype": "Data", - "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": "Workflow State Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 + "reqd": 1 } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-random", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2018-06-11 10:45:46.418470", + "links": [], + "modified": "2020-07-16 04:29:20.898040", "modified_by": "Administrator", "module": "Workflow", "name": "Workflow", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, - "report": 0, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 1, + "sort_field": "modified", "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/www/printview.py b/frappe/www/printview.py index 4853bf1cb9..545e5d581d 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -8,7 +8,7 @@ from frappe import _ from frappe.modules import get_doc_path from frappe.core.doctype.access_log.access_log import make_access_log -from frappe.utils import cint, strip_html +from frappe.utils import cint, sanitize_html, strip_html from six import string_types no_cache = 1 @@ -20,9 +20,9 @@ def get_context(context): """Build context for print""" if not ((frappe.form_dict.doctype and frappe.form_dict.name) or frappe.form_dict.doc): return { - "body": """

Error

+ "body": sanitize_html("""

Error

Parameters doctype and name required

-
%s
""" % repr(frappe.form_dict) +
%s
""" % repr(frappe.form_dict)) } if frappe.form_dict.doc: diff --git a/requirements.txt b/requirements.txt index 0d01886f05..e0ca1a6fad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,8 +13,8 @@ dropbox==9.1.0 email-reply-parser==0.5.9 Faker==2.0.4 future==0.18.2 -GitPython==2.1.15 gitdb2==2.0.6;python_version<'3.4' +GitPython==2.1.15 google-api-python-client==1.9.3 google-auth-httplib2==0.0.3 google-auth-oauthlib==0.4.1 @@ -40,6 +40,7 @@ psycopg2-binary==2.8.4 pyasn1==0.4.8 PyJWT==1.7.1 PyMySQL==0.9.3 +pyngrok==4.1.6 pyOpenSSL==19.1.0 pyotp==2.3.0 PyPDF2==1.26.0 @@ -64,6 +65,6 @@ unittest-xml-reporting==2.5.2 urllib3==1.25.8 watchdog==0.8.0 Werkzeug==0.16.1 +Whoosh==2.7.4 xlrd==1.2.0 zxcvbn-python==4.4.24 -Whoosh==2.7.4