@@ -236,7 +236,7 @@ def errprint(msg): | |||
:param msg: Message.""" | |||
msg = as_unicode(msg) | |||
if not request or (not "cmd" in local.form_dict): | |||
if not request or (not "cmd" in local.form_dict) or conf.developer_mode: | |||
print msg.encode('utf-8') | |||
error_log.append(msg) | |||
@@ -59,27 +59,38 @@ 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, user=None): | |||
def get_data(self, filters=None, limit=None, user=None, as_dict=False): | |||
columns = [] | |||
out = [] | |||
if self.report_type in ('Query Report', 'Script Report'): | |||
# query and script reports | |||
data = frappe.desk.query_report.run(self.name, filters=filters, user=user) | |||
columns_list = [] | |||
for d in data.get('columns'): | |||
if isinstance(d, dict): | |||
columns_list.append(d.get('label')) | |||
columns.append(frappe._dict(d)) | |||
else: | |||
columns_list.append(d.split(':')[0]) | |||
parts = d.split(':') | |||
fieldtype, options = parts[1], None | |||
if fieldtype and '/' in fieldtype: | |||
fieldtype, options = fieldtype.split('/') | |||
columns.append(frappe._dict(label=parts[0], fieldtype=fieldtype, fieldname=parts[0])) | |||
out.append(columns_list) | |||
out += data.get('result') | |||
else: | |||
# standard report | |||
params = json.loads(self.json) | |||
columns = params.get('columns') | |||
filters = params.get('filters') | |||
_filters = params.get('filters') or [] | |||
if filters: | |||
print filters | |||
for key, value in filters.iteritems(): | |||
condition, _value = '=', value | |||
if isinstance(value, (list, tuple)): | |||
condition, _value = value | |||
_filters.append([key, condition, _value]) | |||
def _format(parts): | |||
# sort by is saved as DocType.fieldname, covert it to sql | |||
@@ -90,14 +101,26 @@ class Report(Document): | |||
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, user=user) | |||
filters=_filters, order_by = order_by, as_list=True, limit=limit, user=user, debug=True) | |||
meta = frappe.get_meta(self.ref_doctype) | |||
out.append([meta.get_label(c[0]) for c in columns]) | |||
columns = [meta.get_field(c[0]) or frappe._dict(label=meta.get_label(c[0]), fieldname=c[0]) | |||
for c in columns] | |||
out = out + [list(d) for d in result] | |||
return out | |||
if as_dict: | |||
data = [] | |||
for row in out: | |||
_row = frappe._dict() | |||
data.append(_row) | |||
for i, val in enumerate(row): | |||
_row[columns[i].get('fieldname')] = val | |||
else: | |||
data = out | |||
return columns, data | |||
@Document.whitelist | |||
@@ -16,14 +16,14 @@ class TestReport(unittest.TestCase): | |||
frappe.get_doc(json.loads(f.read())).insert() | |||
report = frappe.get_doc('Report', 'User Activity Report') | |||
data = report.get_data() | |||
columns, 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'}) | |||
columns, data = report.get_data(filters={'user': 'Administrator', 'doctype': 'DocType'}) | |||
self.assertEquals(data[0][0], 'Name') | |||
self.assertEquals(data[0][1], 'Module') | |||
self.assertTrue('User' in [d[0] for d in data]) | |||
@@ -48,6 +48,9 @@ frappe.ui.form.on('Auto Email Report', { | |||
} | |||
} | |||
}, | |||
report: function(frm) { | |||
frm.set_value('filters', ''); | |||
}, | |||
show_filters: function(frm) { | |||
var wrapper = $(frm.get_field('filters_display').wrapper); | |||
wrapper.empty(); | |||
@@ -185,6 +185,36 @@ | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "24", | |||
"depends_on": "eval:doc.report_type=='Report Builder'", | |||
"fieldname": "data_modified_till", | |||
"fieldtype": "Int", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Send Records Updated in Last X Hours", | |||
"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, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
@@ -539,7 +569,7 @@ | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2016-11-07 05:50:31.903959", | |||
"modified": "2016-12-26 12:46:41.193379", | |||
"modified_by": "Administrator", | |||
"module": "Email", | |||
"name": "Auto Email Report", | |||
@@ -3,9 +3,10 @@ | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import frappe, json | |||
from frappe import _ | |||
from frappe.model.document import Document | |||
from datetime import timedelta | |||
import frappe.utils | |||
from frappe.utils.xlsutils import get_xls | |||
from frappe.utils.csvutils import to_csv | |||
@@ -42,29 +43,45 @@ class AutoEmailReport(Document): | |||
def get_report_content(self): | |||
'''Returns file in for the report in given format''' | |||
report = frappe.get_doc('Report', self.report) | |||
raw = report.get_data(limit=self.no_of_rows or 100, user = self.user, filters = self.filters) | |||
if len(raw)==1 and self.send_if_data: | |||
if self.report_type=='Report Builder' and self.data_modified_till: | |||
self.filters = json.loads(self.filters) if self.filters else {} | |||
self.filters['modified'] = ('>', frappe.utils.now_datetime() - timedelta(hours=self.data_modified_till)) | |||
columns, data = report.get_data(limit=self.no_of_rows or 100, user = self.user, | |||
filters = self.filters, as_dict=True) | |||
if len(data)==1 and self.send_if_data: | |||
return None | |||
if self.format == 'HTML': | |||
return self.get_html_table(raw) | |||
return self.get_html_table(columns, data) | |||
elif self.format == 'XLS': | |||
return get_xls(raw) | |||
return get_xls(columns, data) | |||
elif self.format == 'CSV': | |||
return to_csv(raw) | |||
return self.get_csv(columns, data) | |||
else: | |||
frappe.throw(_('Invalid Output Format')) | |||
def get_html_table(self, data): | |||
def get_html_table(self, columns, data): | |||
return frappe.render_template('frappe/templates/includes/print_table.html', { | |||
'headings': data[0], | |||
'columns': columns, | |||
'data': data[1:] | |||
}) | |||
def get_csv(self, columns, data): | |||
out = [[df.label for df in columns], ] | |||
for row in data: | |||
new_row = [] | |||
out.append(new_row) | |||
for df in columns: | |||
new_row.append(frappe.format(row[df.fieldname], df, row)) | |||
return to_csv(out) | |||
def get_file_name(self): | |||
return "{0}.{1}".format(self.report.replace(" ", "-").replace("/", "-"), self.format.lower()) | |||
@@ -9,4 +9,31 @@ import unittest, json | |||
# test_records = frappe.get_test_records('Auto Email Report') | |||
class TestAutoEmailReport(unittest.TestCase): | |||
pass | |||
def test_auto_email(self): | |||
frappe.delete_doc('Auto Email Report', 'Permitted Documents For User') | |||
auto_email_report = frappe.get_doc(dict( | |||
doctype='Auto Email Report', | |||
report='Permitted Documents For User', | |||
report_type='Script Report', | |||
user='Administrator', | |||
enabled=1, | |||
email_to='test@example.com', | |||
format='HTML', | |||
frequency='Daily', | |||
filters=json.dumps(dict(user='Administrator', doctype='DocType')) | |||
)).insert() | |||
data = auto_email_report.get_report_content() | |||
self.assertTrue('<td>DocShare</td>' in data) | |||
self.assertTrue('<td>Core</td>' in data) | |||
auto_email_report.format = 'CSV' | |||
data = auto_email_report.get_report_content() | |||
self.assertTrue('"Language","Core"' in data) | |||
auto_email_report.format = 'XLS' | |||
data = auto_email_report.get_report_content() | |||
@@ -12,6 +12,7 @@ from frappe.utils import flt, cint, getdate, get_datetime, get_time, make_filter | |||
from frappe import _ | |||
from frappe.model import optional_fields | |||
from frappe.model.utils.list_settings import get_list_settings, update_list_settings | |||
from datetime import datetime | |||
class DatabaseQuery(object): | |||
def __init__(self, doctype): | |||
@@ -263,6 +264,8 @@ class DatabaseQuery(object): | |||
f = get_filter(self.doctype, f) | |||
print f | |||
tname = ('`tab' + f.doctype + '`') | |||
if not tname in self.tables: | |||
self.append_table(tname) | |||
@@ -297,11 +300,12 @@ class DatabaseQuery(object): | |||
get_datetime(f.value[0]).strftime("%Y-%m-%d %H:%M:%S.%f"), | |||
add_to_date(get_datetime(f.value[1]),days=1).strftime("%Y-%m-%d %H:%M:%S.%f")) | |||
fallback = "'0000-00-00 00:00:00'" | |||
elif df and df.fieldtype=="Date": | |||
value = getdate(f.value).strftime("%Y-%m-%d") | |||
fallback = "'0000-00-00'" | |||
elif df and df.fieldtype=="Datetime": | |||
elif (df and df.fieldtype=="Datetime") or isinstance(f.value, datetime): | |||
value = get_datetime(f.value).strftime("%Y-%m-%d %H:%M:%S.%f") | |||
fallback = "'0000-00-00 00:00:00'" | |||
@@ -849,10 +849,6 @@ li .footer-child-item { | |||
.blog-text p { | |||
margin-bottom: 30px; | |||
} | |||
.blogger-name { | |||
margin-bottom: 0px; | |||
margin-top: 0px; | |||
} | |||
.comment-view { | |||
padding-bottom: 30px; | |||
} | |||
@@ -921,3 +917,12 @@ li .footer-child-item { | |||
margin-top: -10px; | |||
margin-right: -8px; | |||
} | |||
.page-card { | |||
max-width: 360px; | |||
padding: 30px; | |||
margin: auto; | |||
border: 1px solid #d1d8dd; | |||
border-radius: 4px; | |||
margin: 0 auto; | |||
background-color: #fff; | |||
} |
@@ -584,11 +584,6 @@ li .footer-child-item { | |||
} | |||
} | |||
.blogger-name { | |||
margin-bottom:0px; | |||
margin-top:0px; | |||
} | |||
.comment-view { | |||
padding-bottom: 30px; | |||
} | |||
@@ -661,3 +656,13 @@ li .footer-child-item { | |||
margin-top: -10px; | |||
margin-right: -8px; | |||
} | |||
.page-card { | |||
max-width: 360px; | |||
padding: 30px; | |||
margin: auto; | |||
border: 1px solid @border-color; | |||
border-radius: 4px; | |||
margin: 0 auto; | |||
background-color: #fff; | |||
} |
@@ -1,5 +1,13 @@ | |||
/* login-css */ | |||
body { | |||
background-color: #f5f7fa; | |||
} | |||
footer { | |||
background-color: #ffffff; | |||
} | |||
.page-sidebar, #wrap-footer, .page-header { | |||
display: none; | |||
} | |||
@@ -36,13 +44,6 @@ | |||
} | |||
.form-signin { | |||
max-width: 360px; | |||
padding-right: 30px; | |||
padding-left: 30px; | |||
padding-top: 50px; | |||
margin: 0 auto; | |||
border-radius: 5px; | |||
background-color: #fff; | |||
} | |||
.form-signin .form-signin-heading, | |||
.form-signin .checkbox { | |||
@@ -96,13 +97,11 @@ h5:before { | |||
left: 0; | |||
} | |||
.login_header{ | |||
font-size: 36px; | |||
font-size: 30px; | |||
position: relative; | |||
text-align: center; | |||
margin-bottom:20px; | |||
text-transform: uppercase; | |||
margin: 10px 0px 30px 0px; | |||
letter-spacing: 0.5px; | |||
font-weight: 300; | |||
} | |||
p{ | |||
@@ -1,9 +1,13 @@ | |||
{% macro get_align(col) %} | |||
{%- if col.fieldtype in ('Int', 'Float', 'Currency', 'Check') %} style='text-align: right'{% endif -%} | |||
{% endmacro %} | |||
<table cellpadding=2px cellspacing=0 border=1px style='width:100%; border-collapse:collapse;'> | |||
<thead> | |||
<tr> | |||
{% for col in headings %} | |||
<th> | |||
{{ col }} | |||
{% for col in columns %} | |||
<th {{- get_align(col) }}> | |||
{{- col.label -}} | |||
</th> | |||
{% endfor %} | |||
</tr> | |||
@@ -11,9 +15,9 @@ | |||
<tbody> | |||
{% for row in data %} | |||
<tr> | |||
{% for val in row %} | |||
<td> | |||
{{ frappe.format(val) }} | |||
{% for col in columns %} | |||
<td {{- get_align(col) }}> | |||
{{- frappe.format(row[col.fieldname], col, row) -}} | |||
</td> | |||
{% endfor %} | |||
</td> | |||
@@ -3,7 +3,7 @@ from __future__ import unicode_literals | |||
import frappe, xlwt, StringIO, datetime | |||
from frappe import _ | |||
def get_xls(data): | |||
def get_xls(columns, data): | |||
'''Convert data to xls''' | |||
stream = StringIO.StringIO() | |||
workbook = xlwt.Workbook() | |||
@@ -14,18 +14,22 @@ def get_xls(data): | |||
(frappe.defaults.get_global_default("date_format") or "yyyy-mm-dd")) | |||
bold = xlwt.easyxf('font: bold 1') | |||
# header | |||
for i, col in enumerate(columns): | |||
sheet.write(0, i, col.label, bold) | |||
for i, row in enumerate(data): | |||
for j, val in enumerate(row): | |||
for j, df in enumerate(columns): | |||
f = None | |||
val = row[columns[j].fieldname] | |||
if isinstance(val, (datetime.datetime, datetime.date)): | |||
f = dateformat | |||
if i==0: | |||
f = bold | |||
if f: | |||
sheet.write(i, j, val, f) | |||
sheet.write(i+1, j, val, f) | |||
else: | |||
sheet.write(i, j, val) | |||
sheet.write(i+1, j, frappe.format(val, df, row)) | |||
workbook.save(stream) | |||
stream.seek(0) | |||
@@ -9,10 +9,10 @@ | |||
{% block page_content %} | |||
<!-- {{ for_test }} --> | |||
<div class="login-content"> | |||
<div class="login-content page-card"> | |||
<form class="form-signin form-login" role="form"> | |||
<div class="login_header">Login</div> | |||
<div class="login_header">Sign In</div> | |||
<input type="text" id="login_email" | |||
class="form-control" placeholder="{{ _('Email address') }}" required autofocus> | |||