@@ -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)) |
@@ -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" | |||
} | |||
''' |
@@ -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] | |||
@@ -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 | |||
@@ -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 += "</ol>" | |||
self.db.sql('''insert into help(path, content, title, intro, full_path) values (%s, %s, %s, %s, %s)''', | |||
('/documentation/index', doc_contents, 'Documentation', '', '')) | |||
@@ -37,3 +37,4 @@ cryptography | |||
zxcvbn | |||
psutil | |||
unittest-xml-reporting | |||
xlwt |