@@ -236,7 +236,7 @@ def errprint(msg): | |||||
:param msg: Message.""" | :param msg: Message.""" | ||||
msg = as_unicode(msg) | 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') | print msg.encode('utf-8') | ||||
error_log.append(msg) | error_log.append(msg) | ||||
@@ -59,27 +59,38 @@ class Report(Document): | |||||
make_boilerplate("controller.py", self, {"name": self.name}) | make_boilerplate("controller.py", self, {"name": self.name}) | ||||
make_boilerplate("controller.js", 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 = [] | out = [] | ||||
if self.report_type in ('Query Report', 'Script Report'): | if self.report_type in ('Query Report', 'Script Report'): | ||||
# query and script reports | # query and script reports | ||||
data = frappe.desk.query_report.run(self.name, filters=filters, user=user) | data = frappe.desk.query_report.run(self.name, filters=filters, user=user) | ||||
columns_list = [] | |||||
for d in data.get('columns'): | for d in data.get('columns'): | ||||
if isinstance(d, dict): | if isinstance(d, dict): | ||||
columns_list.append(d.get('label')) | |||||
columns.append(frappe._dict(d)) | |||||
else: | 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') | out += data.get('result') | ||||
else: | else: | ||||
# standard report | # standard report | ||||
params = json.loads(self.json) | params = json.loads(self.json) | ||||
columns = params.get('columns') | 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): | def _format(parts): | ||||
# sort by is saved as DocType.fieldname, covert it to sql | # 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') | 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], | 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) | 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] | 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 | @Document.whitelist | ||||
@@ -16,14 +16,14 @@ class TestReport(unittest.TestCase): | |||||
frappe.get_doc(json.loads(f.read())).insert() | frappe.get_doc(json.loads(f.read())).insert() | ||||
report = frappe.get_doc('Report', 'User Activity Report') | 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][0], 'ID') | ||||
self.assertEquals(data[0][1], 'User Type') | self.assertEquals(data[0][1], 'User Type') | ||||
self.assertTrue('Administrator' in [d[0] for d in data]) | self.assertTrue('Administrator' in [d[0] for d in data]) | ||||
def test_query_report(self): | def test_query_report(self): | ||||
report = frappe.get_doc('Report', 'Permitted Documents For User') | 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][0], 'Name') | ||||
self.assertEquals(data[0][1], 'Module') | self.assertEquals(data[0][1], 'Module') | ||||
self.assertTrue('User' in [d[0] for d in data]) | 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) { | show_filters: function(frm) { | ||||
var wrapper = $(frm.get_field('filters_display').wrapper); | var wrapper = $(frm.get_field('filters_display').wrapper); | ||||
wrapper.empty(); | wrapper.empty(); | ||||
@@ -185,6 +185,36 @@ | |||||
"set_only_once": 0, | "set_only_once": 0, | ||||
"unique": 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, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
@@ -539,7 +569,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2016-11-07 05:50:31.903959", | |||||
"modified": "2016-12-26 12:46:41.193379", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Email", | "module": "Email", | ||||
"name": "Auto Email Report", | "name": "Auto Email Report", | ||||
@@ -3,9 +3,10 @@ | |||||
# For license information, please see license.txt | # For license information, please see license.txt | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | |||||
import frappe, json | |||||
from frappe import _ | from frappe import _ | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from datetime import timedelta | |||||
import frappe.utils | import frappe.utils | ||||
from frappe.utils.xlsutils import get_xls | from frappe.utils.xlsutils import get_xls | ||||
from frappe.utils.csvutils import to_csv | from frappe.utils.csvutils import to_csv | ||||
@@ -42,29 +43,45 @@ class AutoEmailReport(Document): | |||||
def get_report_content(self): | def get_report_content(self): | ||||
'''Returns file in for the report in given format''' | '''Returns file in for the report in given format''' | ||||
report = frappe.get_doc('Report', self.report) | 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 | return None | ||||
if self.format == 'HTML': | if self.format == 'HTML': | ||||
return self.get_html_table(raw) | |||||
return self.get_html_table(columns, data) | |||||
elif self.format == 'XLS': | elif self.format == 'XLS': | ||||
return get_xls(raw) | |||||
return get_xls(columns, data) | |||||
elif self.format == 'CSV': | elif self.format == 'CSV': | ||||
return to_csv(raw) | |||||
return self.get_csv(columns, data) | |||||
else: | else: | ||||
frappe.throw(_('Invalid Output Format')) | 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', { | return frappe.render_template('frappe/templates/includes/print_table.html', { | ||||
'headings': data[0], | |||||
'columns': columns, | |||||
'data': data[1:] | '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): | def get_file_name(self): | ||||
return "{0}.{1}".format(self.report.replace(" ", "-").replace("/", "-"), self.format.lower()) | 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') | # test_records = frappe.get_test_records('Auto Email Report') | ||||
class TestAutoEmailReport(unittest.TestCase): | 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 import _ | ||||
from frappe.model import optional_fields | from frappe.model import optional_fields | ||||
from frappe.model.utils.list_settings import get_list_settings, update_list_settings | from frappe.model.utils.list_settings import get_list_settings, update_list_settings | ||||
from datetime import datetime | |||||
class DatabaseQuery(object): | class DatabaseQuery(object): | ||||
def __init__(self, doctype): | def __init__(self, doctype): | ||||
@@ -263,6 +264,8 @@ class DatabaseQuery(object): | |||||
f = get_filter(self.doctype, f) | f = get_filter(self.doctype, f) | ||||
print f | |||||
tname = ('`tab' + f.doctype + '`') | tname = ('`tab' + f.doctype + '`') | ||||
if not tname in self.tables: | if not tname in self.tables: | ||||
self.append_table(tname) | self.append_table(tname) | ||||
@@ -297,11 +300,12 @@ class DatabaseQuery(object): | |||||
get_datetime(f.value[0]).strftime("%Y-%m-%d %H:%M:%S.%f"), | 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")) | 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'" | fallback = "'0000-00-00 00:00:00'" | ||||
elif df and df.fieldtype=="Date": | elif df and df.fieldtype=="Date": | ||||
value = getdate(f.value).strftime("%Y-%m-%d") | value = getdate(f.value).strftime("%Y-%m-%d") | ||||
fallback = "'0000-00-00'" | 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") | value = get_datetime(f.value).strftime("%Y-%m-%d %H:%M:%S.%f") | ||||
fallback = "'0000-00-00 00:00:00'" | fallback = "'0000-00-00 00:00:00'" | ||||
@@ -849,10 +849,6 @@ li .footer-child-item { | |||||
.blog-text p { | .blog-text p { | ||||
margin-bottom: 30px; | margin-bottom: 30px; | ||||
} | } | ||||
.blogger-name { | |||||
margin-bottom: 0px; | |||||
margin-top: 0px; | |||||
} | |||||
.comment-view { | .comment-view { | ||||
padding-bottom: 30px; | padding-bottom: 30px; | ||||
} | } | ||||
@@ -921,3 +917,12 @@ li .footer-child-item { | |||||
margin-top: -10px; | margin-top: -10px; | ||||
margin-right: -8px; | 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 { | .comment-view { | ||||
padding-bottom: 30px; | padding-bottom: 30px; | ||||
} | } | ||||
@@ -661,3 +656,13 @@ li .footer-child-item { | |||||
margin-top: -10px; | margin-top: -10px; | ||||
margin-right: -8px; | 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 */ | /* login-css */ | ||||
body { | |||||
background-color: #f5f7fa; | |||||
} | |||||
footer { | |||||
background-color: #ffffff; | |||||
} | |||||
.page-sidebar, #wrap-footer, .page-header { | .page-sidebar, #wrap-footer, .page-header { | ||||
display: none; | display: none; | ||||
} | } | ||||
@@ -36,13 +44,6 @@ | |||||
} | } | ||||
.form-signin { | .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 .form-signin-heading, | ||||
.form-signin .checkbox { | .form-signin .checkbox { | ||||
@@ -96,13 +97,11 @@ h5:before { | |||||
left: 0; | left: 0; | ||||
} | } | ||||
.login_header{ | .login_header{ | ||||
font-size: 36px; | |||||
font-size: 30px; | |||||
position: relative; | position: relative; | ||||
text-align: center; | text-align: center; | ||||
margin-bottom:20px; | |||||
text-transform: uppercase; | |||||
margin: 10px 0px 30px 0px; | |||||
letter-spacing: 0.5px; | letter-spacing: 0.5px; | ||||
font-weight: 300; | |||||
} | } | ||||
p{ | 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;'> | <table cellpadding=2px cellspacing=0 border=1px style='width:100%; border-collapse:collapse;'> | ||||
<thead> | <thead> | ||||
<tr> | <tr> | ||||
{% for col in headings %} | |||||
<th> | |||||
{{ col }} | |||||
{% for col in columns %} | |||||
<th {{- get_align(col) }}> | |||||
{{- col.label -}} | |||||
</th> | </th> | ||||
{% endfor %} | {% endfor %} | ||||
</tr> | </tr> | ||||
@@ -11,9 +15,9 @@ | |||||
<tbody> | <tbody> | ||||
{% for row in data %} | {% for row in data %} | ||||
<tr> | <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> | </td> | ||||
{% endfor %} | {% endfor %} | ||||
</td> | </td> | ||||
@@ -3,7 +3,7 @@ from __future__ import unicode_literals | |||||
import frappe, xlwt, StringIO, datetime | import frappe, xlwt, StringIO, datetime | ||||
from frappe import _ | from frappe import _ | ||||
def get_xls(data): | |||||
def get_xls(columns, data): | |||||
'''Convert data to xls''' | '''Convert data to xls''' | ||||
stream = StringIO.StringIO() | stream = StringIO.StringIO() | ||||
workbook = xlwt.Workbook() | workbook = xlwt.Workbook() | ||||
@@ -14,18 +14,22 @@ def get_xls(data): | |||||
(frappe.defaults.get_global_default("date_format") or "yyyy-mm-dd")) | (frappe.defaults.get_global_default("date_format") or "yyyy-mm-dd")) | ||||
bold = xlwt.easyxf('font: bold 1') | 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 i, row in enumerate(data): | ||||
for j, val in enumerate(row): | |||||
for j, df in enumerate(columns): | |||||
f = None | f = None | ||||
val = row[columns[j].fieldname] | |||||
if isinstance(val, (datetime.datetime, datetime.date)): | if isinstance(val, (datetime.datetime, datetime.date)): | ||||
f = dateformat | f = dateformat | ||||
if i==0: | |||||
f = bold | |||||
if f: | if f: | ||||
sheet.write(i, j, val, f) | |||||
sheet.write(i+1, j, val, f) | |||||
else: | else: | ||||
sheet.write(i, j, val) | |||||
sheet.write(i+1, j, frappe.format(val, df, row)) | |||||
workbook.save(stream) | workbook.save(stream) | ||||
stream.seek(0) | stream.seek(0) | ||||
@@ -9,10 +9,10 @@ | |||||
{% block page_content %} | {% block page_content %} | ||||
<!-- {{ for_test }} --> | <!-- {{ for_test }} --> | ||||
<div class="login-content"> | |||||
<div class="login-content page-card"> | |||||
<form class="form-signin form-login" role="form"> | <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" | <input type="text" id="login_email" | ||||
class="form-control" placeholder="{{ _('Email address') }}" required autofocus> | class="form-control" placeholder="{{ _('Email address') }}" required autofocus> | ||||