diff --git a/frappe/__init__.py b/frappe/__init__.py index 305e5b0e0c..1549b71f85 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -17,7 +17,7 @@ from faker import Faker from .exceptions import * from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader) -__version__ = '10.1.48' +__version__ = '10.1.49' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/core/doctype/data_import/data_import.js b/frappe/core/doctype/data_import/data_import.js index de4ab8a514..36c2488aab 100644 --- a/frappe/core/doctype/data_import/data_import.js +++ b/frappe/core/doctype/data_import/data_import.js @@ -17,6 +17,9 @@ frappe.ui.form.on('Data Import', { }; }); + // should never check public + frm.fields_dict["import_file"].df.is_private = 1; + frappe.realtime.on("data_import_progress", function(data) { if (data.data_import === frm.doc.name) { if (data.reload && data.reload === true) { diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index d11d7f0a51..a7b1c4d4dc 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -73,6 +73,9 @@ class Report(Document): return True def update_report_json(self): + if not self.json: + self.json = '{}' + if self.json: data = json.loads(self.json) data["add_total_row"] = self.add_total_row @@ -121,7 +124,15 @@ class Report(Document): else: # standard report params = json.loads(self.json) - columns = params.get('columns') + + if 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: @@ -135,7 +146,11 @@ class Report(Document): # sort by is saved as DocType.fieldname, covert it to sql return '`tab{0}`.`{1}`'.format(*parts) - order_by = _format(params.get('sort_by').split('.')) + ' ' + params.get('sort_order') + if params.get('sort_by'): + order_by = _format(params.get('sort_by').split('.')) + ' ' + params.get('sort_order') + 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') diff --git a/frappe/core/doctype/transaction_log/transaction_log.json b/frappe/core/doctype/transaction_log/transaction_log.json index 33e7ffd493..55aa73b781 100644 --- a/frappe/core/doctype/transaction_log/transaction_log.json +++ b/frappe/core/doctype/transaction_log/transaction_log.json @@ -14,6 +14,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -40,10 +41,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -69,10 +72,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -99,10 +104,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -129,10 +136,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -158,10 +167,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -188,10 +199,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -218,10 +231,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -247,10 +262,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -277,10 +294,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -307,10 +326,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -337,16 +358,18 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "fieldname": "data", - "fieldtype": "Text", + "fieldtype": "Long Text", "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -367,10 +390,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -397,6 +422,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -410,7 +436,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-02-06 11:56:43.519455", + "modified": "2018-09-21 08:49:07.915376", "modified_by": "Administrator", "module": "Core", "name": "Transaction Log", @@ -419,7 +445,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -445,5 +470,6 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/frappe/core/doctype/transaction_log/transaction_log.py b/frappe/core/doctype/transaction_log/transaction_log.py index 748f380f6a..245b953f73 100644 --- a/frappe/core/doctype/transaction_log/transaction_log.py +++ b/frappe/core/doctype/transaction_log/transaction_log.py @@ -20,7 +20,7 @@ class TransactionLog(Document): if prev_hash: self.previous_hash = prev_hash[0][0] else: - self.previous_hash = _("Indexing broken") + self.previous_hash = "Indexing broken" else: self.previous_hash = self.hash_line() self.transaction_hash = self.hash_line() diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index ce470889bc..04b78c0741 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -59,11 +59,11 @@ def generate_report_result(report, filters=None, user=None): method_name = get_report_module_dotted_path(module, report.name) + ".execute" threshold = 10 res = [] - + start_time = datetime.datetime.now() # The JOB res = frappe.get_attr(method_name)(frappe._dict(filters)) - + end_time = datetime.datetime.now() if (end_time - start_time).seconds > threshold and not report.prepared_report: @@ -159,6 +159,8 @@ def run(report_name, filters=None, user=None): frappe.msgprint(_("Must have report permission to access this report."), raise_exception=True) + result = None + if report.prepared_report: if filters: if isinstance(filters, string_types): @@ -167,9 +169,13 @@ def run(report_name, filters=None, user=None): dn = filters.get("prepared_report_name") else: dn = "" - return get_prepared_report_result(report, filters, dn) + result = get_prepared_report_result(report, filters, dn) else: - return generate_report_result(report, filters, user) + result = generate_report_result(report, filters, user) + + result["add_total_row"] = report.add_total_row + + return result def get_prepared_report_result(report, filters, dn=""): diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 46ff6f142f..3d1985ad54 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -165,9 +165,10 @@ def get_context(context): if not frappe.safe_eval(recipient.condition, None, context): continue if recipient.email_by_document_field: - if validate_email_add(doc.get(recipient.email_by_document_field)): - recipient.email_by_document_field = doc.get(recipient.email_by_document_field).replace(",", "\n") - recipients = recipients + recipient.email_by_document_field.split("\n") + email_ids_value = doc.get(recipient.email_by_document_field) + if validate_email_add(email_ids_value): + email_ids = email_ids_value.replace(",", "\n") + recipients = recipients + email_ids.split("\n") # else: # print "invalid email" diff --git a/frappe/email/queue.py b/frappe/email/queue.py index 621a81e2cb..07433ed715 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -88,6 +88,13 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= recipients = [r for r in list(set(recipients)) if r and r not in unsubscribed] + if cc: + cc = [r for r in list(set(cc)) if r and r not in unsubscribed] + + if not recipients and not cc: + # Recipients may have been unsubscribed, exit quietly + return + email_text_context = text_content should_append_unsubscribe = (add_unsubscribe_link diff --git a/frappe/hooks.py b/frappe/hooks.py index 26adacd0f1..c49a39c7c2 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -12,7 +12,7 @@ source_link = "https://github.com/frappe/frappe" app_license = "MIT" develop_version = '11.x.x-develop' -staging_version = '11.0.2' +staging_version = '11.0.3' app_email = "info@frappe.io" @@ -125,7 +125,10 @@ doc_events = { "frappe.desk.notifications.clear_doctype_notifications", "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions" ], - "on_trash": "frappe.desk.notifications.clear_doctype_notifications", + "on_trash": [ + "frappe.desk.notifications.clear_doctype_notifications", + "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions" + ], "on_change": [ "frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_request", ] diff --git a/frappe/permissions.py b/frappe/permissions.py index bc09bc65c7..26fe8a14ea 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -161,7 +161,7 @@ def get_role_permissions(doctype_meta, user=None, verbose=False): def has_permission_without_if_owner_enabled(ptype): return any(p.get(ptype, 0) and not p.get('if_owner', 0) for p in applicable_permissions) - applicable_permissions = list(filter(is_perm_applicable, doctype_meta.permissions)) + applicable_permissions = list(filter(is_perm_applicable, getattr(doctype_meta, 'permissions', []))) has_if_owner_enabled = any(p.get('if_owner', 0) for p in applicable_permissions) for ptype in rights: diff --git a/frappe/printing/doctype/print_settings/print_settings.json b/frappe/printing/doctype/print_settings/print_settings.json index d8fca5c848..4f7039c7f8 100644 --- a/frappe/printing/doctype/print_settings/print_settings.json +++ b/frappe/printing/doctype/print_settings/print_settings.json @@ -305,38 +305,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enable_print_server", - "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": "Enable Print Server", - "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 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -473,7 +441,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:doc.enable_print_server == 1", + "depends_on": "", "fieldname": "server_printer", "fieldtype": "Section Break", "hidden": 0, @@ -499,6 +467,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enable_print_server", + "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": "Enable Print Server", + "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 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -507,6 +507,7 @@ "collapsible": 0, "columns": 0, "default": "localhost", + "depends_on": "enable_print_server", "fieldname": "server_ip", "fieldtype": "Data", "hidden": 0, @@ -539,6 +540,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "enable_print_server", "fieldname": "printer_name", "fieldtype": "Select", "hidden": 0, @@ -564,37 +566,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_17", - "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 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -603,6 +574,7 @@ "collapsible": 0, "columns": 0, "default": "631", + "depends_on": "enable_print_server", "fieldname": "port", "fieldtype": "Int", "hidden": 0, @@ -832,7 +804,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-06-29 01:34:57.508272", + "modified": "2018-09-20 12:10:14.440598", "modified_by": "Administrator", "module": "Printing", "name": "Print Settings", diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index da4a00fd6d..37e75489a4 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -495,7 +495,13 @@ frappe.ui.form.Layout = Class.extend({ var parent = this.frm ? this.frm.doc : null; - if(expression.substr(0,5)=='eval:') { + if(typeof(expression) === 'boolean') { + out = expression; + + } else if(typeof(expression) === 'function') { + out = expression(doc); + + } else if(expression.substr(0,5)=='eval:') { try { out = eval(expression.substr(5)); } catch(e) { diff --git a/frappe/public/js/frappe/ui/base_list.js b/frappe/public/js/frappe/ui/base_list.js index 3e32d893a2..e2b5e8da95 100644 --- a/frappe/public/js/frappe/ui/base_list.js +++ b/frappe/public/js/frappe/ui/base_list.js @@ -265,6 +265,7 @@ frappe.ui.BaseList = Class.extend({ clear: function () { this.data = []; + this.wrapper.find('.list-select-all').prop('checked', false); this.wrapper.find('.result-list').empty(); this.wrapper.find('.result').show(); this.wrapper.find('.no-result').hide(); diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 8ef31b456b..9fb153d079 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -98,10 +98,17 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { () => this.get_report_doc(), () => this.get_report_settings(), () => this.setup_page_head(), - () => this.refresh_report() + () => this.refresh_report(), + () => this.add_make_chart_button() ]); } + add_make_chart_button(){ + this.page.add_inner_button(__("Set Chart"), () => { + this.get_possible_chart_options(); + }); + } + refresh_report() { this.toggle_message(true); @@ -247,9 +254,17 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.add_prepared_report_buttons(data.doc); } this.toggle_message(false); + if (data.result && data.result.length) { - this.render_chart(data); - this.render_report(data); + this.prepare_report_data(data); + + const chart_options = this.get_chart_options(data); + this.$chart.empty(); + if(chart_options) { + this.render_chart(chart_options); + } + + this.render_datatable(); } else { this.toggle_nothing_to_show(true); } @@ -303,12 +318,15 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } } - render_report(data) { + prepare_report_data(data) { + this.raw_data = data; this.columns = this.prepare_columns(data.columns); this.data = this.prepare_data(data.result); this.tree_report = this.data.some(d => 'indent' in d); + } + render_datatable() { if (this.datatable) { this.datatable.refresh(this.data, this.columns); return; @@ -323,21 +341,157 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }); } - render_chart(data) { - this.$chart.empty(); - let opts = this.report_settings.get_chart_data + get_chart_options(data) { + let options = this.report_settings.get_chart_data ? this.report_settings.get_chart_data(data.columns, data.result) : data.chart ? data.chart - : {}; - if (!(opts.data && opts.data.labels && opts.data.labels.length > 0)) return; + : undefined; - Object.assign(opts, { - height: 200 + if (!(options && options.data && options.data.labels && options.data.labels.length > 0)) return; + + return options; + } + + render_chart(options, height=200) { + Object.assign(options, { + height: height }); + this.$chart.empty(); + this.chart = new Chart(this.$chart[0], options); this.$chart.show(); - this.chart = new Chart(this.$chart[0], opts); + } + + get_possible_chart_options() { + const columns = this.raw_data.columns; + const rows = this.raw_data.result; + const first_row = rows[0]; + const has_total_row = this.raw_data.add_total_row; + + const indices = first_row.reduce((accumulator, current_value, current_index) => { + if(!isNaN(Number(current_value))) { + accumulator.push(current_index); + } + return accumulator; + }, []); + + function get_column_values(column_name) { + const column_index = columns.indexOf(column_name); + return rows.map(row => row[column_index]); + } + + function get_chart_options({ y_field, x_field, chart_type, color }) { + const type = chart_type.toLowerCase(); + const colors = color ? [color] : undefined; + + let labels = get_column_values(x_field) + .filter(Boolean) + .map(d => d.trim()) + .filter(Boolean); + + let dataset_values = get_column_values(y_field).map(d => Number(d)); + + if(has_total_row) { + labels = labels.slice(0, -1); + dataset_values = dataset_values.slice(0, -1); + } + + return { + data: { + labels: labels, + datasets: [ + { values: dataset_values } + ] + }, + type: type, + colors: colors + }; + } + + function preview_chart() { + const wrapper = $(dialog.fields_dict["chart_preview"].wrapper); + const values = dialog.get_values(true); + let options = get_chart_options(values); + + Object.assign(options, { + height: 150 + }); + + wrapper.empty(); + new Chart(wrapper[0], options); + wrapper.find('.chart-container .title, .chart-container .sub-title').hide(); + wrapper.show(); + } + + const numeric_fields = columns.filter((col, i) => indices.includes(i)); + const non_numeric_fields = columns.filter((col, i) => !indices.includes(i)) + + const dialog = new frappe.ui.Dialog({ + title: __('Make Chart'), + fields: [ + { + fieldname: 'y_field', + label: 'Y Field', + fieldtype: 'Select', + options: numeric_fields, + default: numeric_fields[0], + onchange: preview_chart + }, + { + fieldname: 'x_field', + label: 'X Field', + fieldtype: 'Select', + options: non_numeric_fields, + default: non_numeric_fields[0], + onchange: preview_chart + }, + { + fieldname: 'cb_1', + fieldtype: 'Column Break' + }, + { + fieldname: 'chart_type', + label: 'Type of Chart', + fieldtype: 'Select', + options: ['Bar', 'Line', 'Percentage', 'Pie'], + default: 'Bar', + onchange: preview_chart + }, + { + fieldname: 'color', + label: 'Color', + fieldtype: 'Color', + depends_on: doc => ['Bar', 'Line'].includes(doc.chart_type), + onchange: preview_chart, + }, + { + fieldname: 'sb_1', + fieldtype: 'Section Break', + label: 'Chart Preview' + }, + { + fieldname: 'chart_preview', + label: 'Chart Preview', + fieldtype: 'HTML', + } + ], + primary_action_label: __('Make'), + primary_action: (values) => { + let options = get_chart_options(values); + + options.title = __(`${this.report_name}: ${values.y_field} vs ${values.x_field}`); + + this.render_chart(options); + + dialog.hide(); + } + }); + + dialog.show(); + + // load preview after dialog animation + setTimeout(preview_chart, 500); } get_user_settings() { diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 468b0c5561..eb30c2cb67 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -306,7 +306,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { this.chart = new Chart(this.$charts_wrapper[0], { title: __("{0} Chart", [this.doctype]), data: data, - type: args.chart_type, // 'bar', 'line', 'scatter', 'pie', 'percentage' + type: args.chart_type, height: 150, colors: ['violet', 'light-blue', 'orange', 'red'], @@ -365,7 +365,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { { label: __('Chart Type'), fieldtype: 'Select', - options: ['Bar', 'Line', 'Scatter', 'Pie', 'Percentage'], + options: ['Bar', 'Line', 'Pie', 'Percentage'], fieldname: 'chart_type', default: toTitle(defaults.chart_type || 'Bar') } @@ -396,6 +396,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { // look like strings and also // monkey patch the doc // javascript is awesome + + // O.o + return { doc: d, toString() { diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less index ef8d0841a4..bdce9331cc 100644 --- a/frappe/public/less/form.less +++ b/frappe/public/less/form.less @@ -386,6 +386,10 @@ h6.uppercase, .h6.uppercase { } } +.timeline-item .media-body { + max-width: 100%; +} + .timeline-item.user-content { margin: 30px 0px 30px 27px; diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py index ab6357cc45..f1194edaf4 100755 --- a/frappe/utils/background_jobs.py +++ b/frappe/utils/background_jobs.py @@ -21,7 +21,7 @@ queue_timeout = { redis_connection = None -def enqueue(method, queue='default', timeout=300, event=None, +def enqueue(method, queue='default', timeout=None, event=None, is_async=True, job_name=None, now=False, enqueue_after_commit=False, **kwargs): ''' Enqueue method to be executed using a background worker diff --git a/frappe/utils/change_log.py b/frappe/utils/change_log.py index 4ac1427b4e..cc97013f1c 100644 --- a/frappe/utils/change_log.py +++ b/frappe/utils/change_log.py @@ -10,8 +10,8 @@ import requests import subprocess # nosec from frappe.utils import cstr from frappe.utils.gitutils import get_app_last_commit_ref, get_app_branch -from frappe import _ import subprocess # nosec +from frappe import _, safe_decode def get_change_log(user=None): if not user: user = frappe.session.user @@ -118,15 +118,21 @@ def get_versions(): def get_app_branch(app): '''Returns branch of an app''' try: - return subprocess.check_output('cd ../apps/{0} && git rev-parse --abbrev-ref HEAD'.format(app), - shell=True).strip() + result = subprocess.check_output('cd ../apps/{0} && git rev-parse --abbrev-ref HEAD'.format(app), + shell=True) + result = safe_decode(result) + result = result.strip() + return result except Exception as e: return '' def get_app_last_commit_ref(app): try: - return subprocess.check_output('cd ../apps/{0} && git rev-parse HEAD'.format(app), - shell=True).strip()[:7] + result = subprocess.check_output('cd ../apps/{0} && git rev-parse HEAD --short 7'.format(app), + shell=True) + result = safe_decode(result) + result = result.strip() + return result except Exception as e: return '' @@ -159,7 +165,7 @@ def check_release_on_github(app): # Check if repo remote is on github from subprocess import CalledProcessError try: - remote_url = subprocess.check_output("cd ../apps/{} && git ls-remote --get-url".format(app), shell=True) + remote_url = subprocess.check_output("cd ../apps/{} && git ls-remote --get-url".format(app), shell=True).decode() except CalledProcessError: # Passing this since some apps may not have git initializaed in them return None diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py index 5eb7a84b0d..e4c2ae3451 100644 --- a/frappe/workflow/doctype/workflow_action/workflow_action.py +++ b/frappe/workflow/doctype/workflow_action/workflow_action.py @@ -24,17 +24,18 @@ def get_permission_query_conditions(user): return "(`tabWorkflow Action`.`user`='{user}')".format(user=user) - def has_permission(doc, user): if user not in ['Administrator', doc.user]: return False - def process_workflow_actions(doc, state): - workflow = get_workflow_name(doc.get('doctype')) if not workflow: return + if state == "on_trash": + clear_workflow_actions(doc.get('doctype'), doc.get('name')) + return + if is_workflow_action_already_created(doc): return clear_old_workflow_actions(doc) @@ -54,7 +55,6 @@ def process_workflow_actions(doc, state): if send_email_alert(workflow): enqueue(send_workflow_action_email, queue='short', users_data=list(user_data_map.values()), doc=doc) - @frappe.whitelist(allow_guest=True) def apply_action(action, doctype, docname, current_state, user=None, last_modified=None): if not verify_request(): @@ -234,6 +234,14 @@ def is_workflow_action_already_created(doc): 'workflow_state': get_doc_workflow_state(doc) }) +def clear_workflow_actions(doctype, name): + if not (doctype and name): + return + + frappe.db.sql('''delete from `tabWorkflow Action` + where reference_doctype=%s and reference_name=%s''', + (doctype, name)) + def get_doc_workflow_state(doc): workflow_name = get_workflow_name(doc.get('doctype')) workflow_state_field = get_workflow_state_field(workflow_name)