From ddd45a71bc6f71be277647e69cbe60ebf9cc11e8 Mon Sep 17 00:00:00 2001 From: Daizy Date: Mon, 7 Feb 2022 19:35:12 +0530 Subject: [PATCH] feat: Allow users to customize their own reports --- frappe/core/doctype/report/test_report.py | 52 ++++++++++++++++++ frappe/desk/reportview.py | 42 +++++++++++---- .../js/frappe/views/reports/report_view.js | 53 +++++++++++++++---- 3 files changed, 129 insertions(+), 18 deletions(-) diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 36e3b09254..cd4c2f4553 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -4,6 +4,7 @@ import frappe, json, os import unittest from frappe.desk.query_report import run, save_report +from frappe.desk.reportview import delete_report, save_report as save_as_report from frappe.custom.doctype.customize_form.customize_form import reset_customization test_records = frappe.get_test_records('Report') @@ -30,6 +31,57 @@ class TestReport(unittest.TestCase): self.assertEqual(columns[1].get('label'), 'Module') self.assertTrue('User' in [d.get('name') for d in data]) + def test_can_save_or_delete_report(self): + '''Test case to test if if users can create, save or delete their own report of type Report Builder''' + frappe.set_user("Administrator") + + report = frappe.get_doc({ + 'doctype': 'Report', + 'ref_doctype': 'User', + 'report_name': 'Test Delete Report', + 'report_type': 'Report Builder', + 'is_standard': 'No', + }).insert() + + frappe.set_user("test@example.com") + self.assertRaisesRegex(frappe.exceptions.ValidationError, "Only Report owner or Report Manager can delete the reports", delete_report, report.name) + + frappe.set_user("Administrator") + + report.report_type = 'Custom Report' # change report type to validate + report.save() + + self.assertRaisesRegex(frappe.exceptions.ValidationError, "Only reports of type Report Builder can be deleted", delete_report, report.name) + + report.is_standard = 'Yes' # change is_standard to validate + report.save() + + self.assertRaisesRegex(frappe.exceptions.ValidationError, "Standard Reports can not be deleted", delete_report, report.name) + + frappe.set_user("test@example.com") + + report_name = save_as_report( + 'Dummy Report', + 'User', + json.dumps([{ + 'fieldname': 'email', + 'fieldtype': 'Data', + 'label': 'Email', + 'insert_after_index': 0, + 'link_field': 'name', + 'doctype': 'User', + 'options': 'Email', + 'width': 100, + 'id':'email', + 'name': 'Email' + }]) + ) + + doc = frappe.get_doc("Report", report_name) + + delete_report(doc.name) + + def test_custom_report(self): reset_customization('User') custom_report_name = save_report( diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index c45fc9bfdd..d1eaf00452 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -262,23 +262,47 @@ def compress(data, args=None): } @frappe.whitelist() -def save_report(): - """save report""" +def save_report(name, doctype, report_settings): + """save report if report type is report builder""" - data = frappe.local.form_dict - if frappe.db.exists('Report', data['name']): - d = frappe.get_doc('Report', data['name']) + if frappe.db.exists('Report', name): + d = frappe.get_doc('Report', name) + if d.is_standard == "Yes": + frappe.throw(_("Standard Reports can not be edited")) + + if d.report_type != "Report Builder": + frappe.throw(_("Only reports of type Report Builder can be created")) + + if d.owner != frappe.session.user: + frappe.throw(_("Only Report owner or Report Manager can save the reports")) else: d = frappe.new_doc('Report') - d.report_name = data['name'] - d.ref_doctype = data['doctype'] + d.report_name = name + d.ref_doctype = doctype d.report_type = "Report Builder" - d.json = data['json'] - frappe.get_doc(d).save() + d.json = report_settings + frappe.get_doc(d).save(ignore_permissions=True) frappe.msgprint(_("{0} is saved").format(d.name), alert=True) return d.name +@frappe.whitelist() +def delete_report(name): + """delete report type of report builder if user is report owner or has role Report Manager""" + + report_doc = frappe.get_doc("Report", name) + if report_doc.is_standard == "Yes": + frappe.throw(_("Standard Reports can not be deleted")) + + if report_doc.report_type != "Report Builder": + frappe.throw(_("Only reports of type Report Builder can be deleted")) + + if report_doc.owner != frappe.session.user: + frappe.throw(_("Only Report owner or Report Manager can delete the reports")) + + report_doc.delete(ignore_permissions=True) + frappe.msgprint(_("{0} is Deleted").format(report_doc.name), alert=True) + @frappe.whitelist() @frappe.read_only() def export_query(): diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 23e415ed3e..560657f36d 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -18,14 +18,14 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { setup_defaults() { super.setup_defaults(); this.page_title = __('Report:') + ' ' + this.page_title; - this.menu_items = this.report_menu_items(); - this.view = 'Report'; const route = frappe.get_route(); if (route.length === 4) { this.report_name = route[3]; } + this.view = 'Report'; + if (this.report_name) { return this.get_report_doc() .then(doc => { @@ -39,6 +39,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { this.page_length = this.report_doc.json.page_length || 20; this.order_by = this.report_doc.json.order_by || 'modified desc'; this.chart_args = this.report_doc.json.chart_args; + this.menu_items = this.report_menu_items(); }); } else { this.add_totals_row = this.view_user_settings.add_totals_row || 0; @@ -1207,7 +1208,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { args: { name: name, doctype: this.doctype, - json: JSON.stringify(report_settings) + report_settings: JSON.stringify(report_settings) }, callback:(r) => { if(r.exc) { @@ -1244,6 +1245,25 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { } } + delete_report() { + const _delete_report = (name) => { + return frappe.call({ + method: 'frappe.desk.reportview.delete_report', + args: { name }, + callback: (r) => { + if (r.exc) { + frappe.msgprint(__("Report was not deleted (there were errors)")); + return; + } + } + }); + } + + if (this.report_name) { + _delete_report(this.report_name); + } + } + get_column_widths() { if (this.datatable) { return this.datatable @@ -1465,12 +1485,27 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { } }); - // save buttons - if(frappe.user.is_report_manager()) { - items = items.concat([ - { label: __('Save'), action: () => this.save_report('save') }, - { label: __('Save As'), action: () => this.save_report('save_as') } - ]); + const can_save_or_delete = this.report_doc.owner === frappe.session.user || frappe.user.is_report_manager() + // A user with role Report Manager or Report Owner can save + if (can_save_or_delete) { + items.push({ + label: __("Save"), + action: () => this.save_report('save') + }); + } + + // anyone can save as + items.push({ + label: __('Save As'), + action: () => this.save_report('save_as') + }); + + // A user with role Report Manager or Report Owner can delete + if (can_save_or_delete) { + items.push({ + label: __("Delete"), + action: () => this.delete_report() + }); } // user permissions