diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index b7f63b7c91..13e8b1c39e 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -3,7 +3,9 @@ from __future__ import unicode_literals import frappe +import json from frappe import _ +import frappe.desk.query_report from frappe.utils import cint from frappe.model.document import Document from frappe.modules.export_file import export_to_files @@ -48,6 +50,39 @@ class Report(Document): make_boilerplate("controller.py", self, {"name": self.name}) make_boilerplate("controller.js", self, {"name": self.name}) + def get_data(self, filters=None, limit=None): + '''Run the report''' + out = [] + + if self.report_type in ('Query Report', 'Script Report'): + # query and script reports + data = frappe.desk.query_report.run(self.name, filters=filters) + out.append([d.split(':')[0] for d in data.get('columns')]) + out += data.get('result') + else: + # standard report + params = json.loads(self.json) + columns = params.get('columns') + filters = params.get('filters') + + def _format(parts): + # 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_next'): + order_by += ', ' + _format(params.get('sort_by_next').split('.')) + ' ' + params.get('sort_order_next') + + result = frappe.get_list(self.ref_doctype, fields = [_format([c[1], c[0]]) for c in columns], + filters=filters, order_by = order_by, as_list=True, limit=limit) + + meta = frappe.get_meta(self.ref_doctype) + + out.append([meta.get_label(c[0]) for c in columns]) + out = out + [list(d) for d in result] + + return out + @Document.whitelist def toggle_disable(self, disable): self.db_set("disabled", cint(disable)) diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 6cc6a6a4ab..2875d5fd13 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -1,10 +1,46 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import frappe +import frappe, json import unittest test_records = frappe.get_test_records('Report') class TestReport(unittest.TestCase): - pass + def test_report_builder(self): + if not frappe.db.exists('Report', 'User Activity Report'): + frappe.get_doc(json.loads(user_activity_report)).insert() + + report = frappe.get_doc('Report', 'User Activity Report') + data = report.get_data() + self.assertEquals(data[0][0], 'ID') + self.assertEquals(data[0][1], 'User Type') + self.assertTrue('Administrator' in [d[0] for d in data]) + + def test_query_report(self): + report = frappe.get_doc('Report', 'Permitted Documents For User') + data = report.get_data(filters={'user': 'Administrator', 'doctype': 'DocType'}) + self.assertEquals(data[0][0], 'Name') + self.assertEquals(data[0][1], 'Module') + self.assertTrue('Auto Email Report' in [d[0] for d in data]) + +# test standard report with child table +user_activity_report = ''' + { + "add_total_row": 0, + "apply_user_permissions": 1, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "is_standard": "No", + "javascript": null, + "json": "{\"filters\":[],\"columns\":[[\"name\",\"User\"],[\"user_type\",\"User\"],[\"first_name\",\"User\"],[\"last_name\",\"User\"],[\"last_active\",\"User\"],[\"role\",\"UserRole\"]],\"sort_by\":\"User.modified\",\"sort_order\":\"desc\",\"sort_by_next\":null,\"sort_order_next\":\"desc\"}", + "modified": "2016-09-01 02:59:07.728890", + "module": "Core", + "name": "User Activity Report", + "query": null, + "ref_doctype": "User", + "report_name": "User Activity Report", + "report_type": "Report Builder" + } +''' diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 31c376e914..9c55d2f8d8 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -60,9 +60,12 @@ def get_script(report_name): } @frappe.whitelist() -def run(report_name, filters=()): +def run(report_name, filters=None): report = get_report_doc(report_name) + if not filters: + filters = [] + if filters and isinstance(filters, basestring): filters = json.loads(filters) @@ -86,13 +89,13 @@ def run(report_name, filters=()): if report.is_standard=="Yes": method_name = get_report_module_dotted_path(module, report.name) + ".execute" res = frappe.get_attr(method_name)(frappe._dict(filters)) - + columns, result = res[0], res[1] if len(res) > 2: message = res[2] if len(res) > 3: chart = res[3] - + if report.apply_user_permissions and result: result = get_filtered_data(report.ref_doctype, columns, result) @@ -124,14 +127,14 @@ def add_total_row(result, columns): else: fieldtype = col.get("fieldtype") options = col.get("options") - + for row in result: if fieldtype in ["Currency", "Int", "Float", "Percent"] and flt(row[i]): total_row[i] = flt(total_row[i]) + flt(row[i]) - + if fieldtype == "Percent" and i not in has_percent: has_percent.append(i) - + if fieldtype=="Link" and options == "Currency": total_row[i] = result[0][i] diff --git a/frappe/model/meta.py b/frappe/model/meta.py index aeb61f9c2b..04de16d88f 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -23,6 +23,7 @@ from frappe.model.document import Document from frappe.model.base_document import BaseDocument from frappe.model.db_schema import type_map from frappe.modules import load_doctype_module +from frappe import _ def get_meta(doctype, cached=True): if cached: @@ -117,7 +118,19 @@ class Meta(Document): return True if self.get_field(fieldname) else False def get_label(self, fieldname): - return self.get_field(fieldname).label + '''Get label of the given fieldname''' + df = self.get_field(fieldname) + if df: + label = df.label + else: + label = { + 'name': _('ID'), + 'owner': _('Created By'), + 'modified_by': _('Modified By'), + 'creation': _('Created On'), + 'modified': _('Last Modified On') + }.get(fieldname) or _('No Label') + return label def get_options(self, fieldname): return self.get_field(fieldname).options diff --git a/frappe/utils/help.py b/frappe/utils/help.py index 159027b1bf..9d607caba7 100644 --- a/frappe/utils/help.py +++ b/frappe/utils/help.py @@ -12,6 +12,7 @@ from frappe.database import Database import os from markdown2 import markdown from bs4 import BeautifulSoup +import jinja2.exceptions def sync(): # make table @@ -113,17 +114,21 @@ class HelpDatabase(object): if fname.rsplit('.', 1)[-1] in ('md', 'html'): fpath = os.path.join(basepath, fname) with open(fpath, 'r') as f: - content = frappe.render_template(unicode(f.read(), 'utf-8'), - {'docs_base_url': '/assets/{app}_docs'.format(app=app)}) - - relpath = self.get_out_path(fpath) - relpath = relpath.replace("user", app) - content = markdown(content) - title = self.make_title(basepath, fname, content) - intro = self.make_intro(content) - content = self.make_content(content, fpath, relpath) - self.db.sql('''insert into help(path, content, title, intro, full_path) - values (%s, %s, %s, %s, %s)''', (relpath, content, title, intro, fpath)) + try: + content = frappe.render_template(unicode(f.read(), 'utf-8'), + {'docs_base_url': '/assets/{app}_docs'.format(app=app)}) + + relpath = self.get_out_path(fpath) + relpath = relpath.replace("user", app) + content = markdown(content) + title = self.make_title(basepath, fname, content) + intro = self.make_intro(content) + content = self.make_content(content, fpath, relpath) + self.db.sql('''insert into help(path, content, title, intro, full_path) + values (%s, %s, %s, %s, %s)''', (relpath, content, title, intro, fpath)) + except jinja2.exceptions.TemplateSyntaxError: + print "Invalid Jinja Template for {0}. Skipping".format(fpath) + doc_contents += "" self.db.sql('''insert into help(path, content, title, intro, full_path) values (%s, %s, %s, %s, %s)''', ('/documentation/index', doc_contents, 'Documentation', '', '')) diff --git a/requirements.txt b/requirements.txt index 5564ddfe15..c198014550 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,3 +37,4 @@ cryptography zxcvbn psutil unittest-xml-reporting +xlwt