Merge branch 'develop' into chart-link

This commit is contained in:
Suraj Shetty 2020-07-15 15:40:44 +05:30 committed by GitHub
commit 87805fdfd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 836 additions and 1349 deletions

View file

@ -333,12 +333,20 @@ class CookieManager:
# sid expires in 3 days
expires = datetime.datetime.now() + datetime.timedelta(days=3)
if frappe.session.sid:
self.cookies["sid"] = {"value": frappe.session.sid, "expires": expires}
self.set_cookie("sid", frappe.session.sid, expires=expires, httponly=True)
if frappe.session.session_country:
self.cookies["country"] = {"value": frappe.session.get("session_country")}
self.set_cookie("country", frappe.session.session_country)
def set_cookie(self, key, value, expires=None):
self.cookies[key] = {"value": value, "expires": expires}
def set_cookie(self, key, value, expires=None, secure=False, httponly=False, samesite="Strict"):
if not secure:
secure = frappe.local.request.scheme == "https"
self.cookies[key] = {
"value": value,
"expires": expires,
"secure": secure,
"httponly": httponly,
"samesite": samesite
}
def delete_cookie(self, to_delete):
if not isinstance(to_delete, (list, tuple)):
@ -349,7 +357,10 @@ class CookieManager:
def flush_cookies(self, response):
for key, opts in self.cookies.items():
response.set_cookie(key, quote((opts.get("value") or "").encode('utf-8')),
expires=opts.get("expires"))
expires=opts.get("expires"),
secure=opts.get("secure"),
httponly=opts.get("httponly"),
samesite=opts.get("samesite"))
# expires yesterday!
expires = datetime.datetime.now() + datetime.timedelta(days=-1)

View file

@ -16,6 +16,13 @@ def get_data():
"description": _("Language, Date and Time settings"),
"hide_count": True
},
{
"type": "doctype",
"name": "Global Defaults",
"label": _("Global Defaults"),
"description": _("Company, Fiscal Year and Currency defaults"),
"hide_count": True
},
{
"type": "doctype",
"name": "Error Log",

View file

@ -18,7 +18,7 @@
{
"hidden": 0,
"label": "Core",
"links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]"
"links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Company, Fiscal Year and Currency defaults\",\n \"hide_count\": true,\n \"label\": \"Global Defaults\",\n \"name\": \"Global Defaults\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@ -39,10 +39,11 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Settings",
"modified": "2020-04-01 11:24:40.636747",
"modified": "2020-07-14 10:09:09.520557",
"modified_by": "Administrator",
"module": "Core",
"name": "Settings",

View file

@ -221,7 +221,7 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None)
:param print_html: Send given value as HTML attachment.
:param print_format: Attach print format of parent document."""
view_link = frappe.utils.cint(frappe.db.get_value("Print Settings", "Print Settings", "attach_view_link"))
view_link = frappe.utils.cint(frappe.db.get_value("System Settings", "System Settings", "attach_view_link"))
if print_format and view_link:
doc.content += get_attach_link(doc, print_format)

View file

@ -441,9 +441,8 @@ class ImportFile:
# if there are child doctypes, find the subsequent rows
if len(doctypes) > 1:
# subsequent rows either dont have any parent value set
# or have the same value as the parent row
# we include a row if either of conditions match
# subsequent rows that have blank values in parent columns
# are considered as child rows
parent_column_indexes = self.header.get_column_indexes(self.doctype)
parent_row_values = first_row.get_values(parent_column_indexes)
@ -454,11 +453,8 @@ class ImportFile:
if all([v in INVALID_VALUES for v in row_values]):
rows.append(row)
continue
# if the row has same values as parent row, it's a child row doc
if row_values == parent_row_values:
rows.append(row)
continue
# if any of those conditions dont match, it's the next doc
# if we encounter a row which has values in parent columns,
# then it is the next doc
break
parent_doc = None
@ -693,6 +689,9 @@ class Row:
return value
def get_date(self, value, column):
if isinstance(value, datetime):
return value
date_format = column.date_format
if date_format:
try:

View file

@ -59,6 +59,7 @@
"column_break_18",
"disable_standard_email_footer",
"hide_footer_in_auto_email_reports",
"attach_view_link",
"chat",
"enable_chat",
"use_socketio_to_upload_file"
@ -422,12 +423,18 @@
"fieldname": "enable_onboarding",
"fieldtype": "Check",
"label": "Enable Onboarding"
},
{
"default": "1",
"fieldname": "attach_view_link",
"fieldtype": "Check",
"label": "Send document Web View link in email"
}
],
"icon": "fa fa-cog",
"issingle": 1,
"links": [],
"modified": "2020-05-01 19:21:15.496065",
"modified": "2020-07-02 16:13:00.166382",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",

View file

@ -5,10 +5,15 @@ frappe.ui.form.on('Dashboard', {
refresh: function(frm) {
frm.add_custom_button(__("Show Dashboard"), () => frappe.set_route('dashboard', frm.doc.name));
if (!frappe.boot.developer_mode) {
frm.disable_form();
}
frm.set_query("chart", "charts", function() {
return {
filters: {
is_public: 1
is_public: 1,
is_standard: 1,
}
};
});
@ -16,7 +21,8 @@ frappe.ui.form.on('Dashboard', {
frm.set_query("card", "cards", function() {
return {
filters: {
is_public: 1
is_public: 1,
is_standard: 1,
}
};
});

View file

@ -1,5 +1,6 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:dashboard_name",
"creation": "2019-01-10 12:54:40.938705",
"doctype": "DocType",
@ -8,6 +9,8 @@
"field_order": [
"dashboard_name",
"is_default",
"is_standard",
"module",
"charts",
"chart_options",
"cards"
@ -35,21 +38,35 @@
"reqd": 1
},
{
"description": "Set Default Options for all charts on this Dashboard (Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"])",
"fieldname": "chart_options",
"fieldtype": "Code",
"label": "Chart Options",
"options": "JSON"
"description": "Set Default Options for all charts on this Dashboard (Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"])",
"fieldname": "chart_options",
"fieldtype": "Code",
"label": "Chart Options",
"options": "JSON"
},
{
"fieldname": "cards",
"fieldtype": "Table",
"label": "Cards",
"options": "Number Card Link"
},
{
"default": "0",
"fieldname": "is_standard",
"fieldtype": "Check",
"label": "Is Standard"
},
{
"depends_on": "eval: doc.is_standard",
"fieldname": "module",
"fieldtype": "Link",
"label": "Module",
"mandatory_depends_on": "eval: doc.is_standard",
"options": "Module Def"
}
],
"links": [],
"modified": "2020-04-29 13:26:37.362482",
"modified": "2020-07-10 17:48:19.468813",
"modified_by": "Administrator",
"module": "Desk",
"name": "Dashboard",

View file

@ -4,6 +4,7 @@
from __future__ import unicode_literals
from frappe.model.document import Document
from frappe.modules.export_file import export_to_files
import frappe
from frappe import _
import json
@ -15,7 +16,23 @@ class Dashboard(Document):
frappe.db.sql('''update
tabDashboard set is_default = 0 where name != %s''', self.name)
if frappe.conf.developer_mode and self.is_standard:
export_to_files(record_list=[['Dashboard', self.name, self.module + ' Dashboard']], record_module=self.module)
def validate(self):
if not frappe.conf.developer_mode and self.is_standard:
frappe.throw('Cannot edit Standard Dashboards')
if self.is_standard:
non_standard_docs_map = {
'Dashboard Chart': get_non_standard_charts_in_dashboard(self),
'Number Card': get_non_standard_cards_in_dashboard(self)
}
if non_standard_docs_map['Dashboard Chart'] or non_standard_docs_map['Number Card']:
message = get_non_standard_warning_message(non_standard_docs_map)
frappe.throw(message, title=_("Standard Not Set"), is_minimizable=True)
self.validate_custom_options()
def validate_custom_options(self):
@ -48,3 +65,29 @@ def get_permitted_cards(dashboard_name):
if frappe.has_permission('Number Card', doc=card.card):
permitted_cards.append(card)
return permitted_cards
def get_non_standard_charts_in_dashboard(dashboard):
non_standard_charts = [doc.name for doc in frappe.get_list('Dashboard Chart', {'is_standard': 0})]
return [chart_link.chart for chart_link in dashboard.charts if chart_link.chart in non_standard_charts]
def get_non_standard_cards_in_dashboard(dashboard):
non_standard_cards = [doc.name for doc in frappe.get_list('Number Card', {'is_standard': 0})]
return [card_link.card for card_link in dashboard.cards if card_link.card in non_standard_cards]
def get_non_standard_warning_message(non_standard_docs_map):
message = _('''Please set the following documents in this Dashboard as standard first.''')
def get_html(docs, doctype):
html = '<p>{}</p>'.format(frappe.bold(doctype))
for doc in docs:
html += '<div><a href="#Form/{doctype}/{doc}">{doc}</a></div>'.format(doctype=doctype, doc=doc)
html += '<br>'
return html
html = message + '<br>'
for doctype in non_standard_docs_map:
if non_standard_docs_map[doctype]:
html += get_html(non_standard_docs_map[doctype], doctype)
return html

View file

@ -9,9 +9,24 @@ frappe.ui.form.on('Dashboard Chart', {
frm.add_fetch('source', 'timeseries', 'timeseries');
},
before_save: function(frm) {
let dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || 'null');
let static_filters = JSON.parse(frm.doc.filters_json || 'null');
static_filters =
frappe.dashboard_utils.remove_common_static_filter_values(static_filters, dynamic_filters);
frm.set_value('filters_json', JSON.stringify(static_filters));
frm.trigger('show_filters');
},
refresh: function(frm) {
frm.chart_filters = null;
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
frm.set_df_property('chart_options_section', 'hidden', 1);
frm.disable_form();
}
frm.add_custom_button('Add Chart to Dashboard', () => {
const d = new frappe.ui.Dialog({
title: __('Add to Dashboard'),
@ -49,6 +64,8 @@ frappe.ui.form.on('Dashboard Chart', {
});
frm.set_df_property("filters_section", "hidden", 1);
frm.set_df_property("dynamic_filters_section", "hidden", 1);
frm.trigger('set_time_series');
frm.set_query('document_type', function() {
return {
@ -66,6 +83,15 @@ frappe.ui.form.on('Dashboard Chart', {
if (!frappe.boot.developer_mode) {
frm.set_df_property("custom_options", "hidden", 1);
}
},
is_standard: function(frm) {
if (frappe.boot.developer_mode && frm.doc.is_standard) {
frm.trigger('render_dynamic_filters_table');
} else {
frm.set_df_property("dynamic_filters_section", "hidden", 1);
}
},
source: function(frm) {
@ -111,6 +137,7 @@ frappe.ui.form.on('Dashboard Chart', {
frm.set_value('based_on', '');
frm.set_value('value_based_on', '');
frm.set_value('filters_json', '[]');
frm.set_value('dynamic_filters_json', '[]');
frm.trigger('update_options');
},
@ -119,6 +146,7 @@ frappe.ui.form.on('Dashboard Chart', {
frm.set_value('y_axis', []);
frm.set_df_property('x_field', 'options', []);
frm.set_value('filters_json', '{}');
frm.set_value('dynamic_filters_json', '{}');
frm.trigger('set_chart_report_filters');
},
@ -146,7 +174,7 @@ frappe.ui.form.on('Dashboard Chart', {
},
set_chart_field_options: function(frm) {
let filters = frm.doc.filters_json.length > 2? JSON.parse(frm.doc.filters_json): null;
let filters = frm.doc.filters_json.length > 2 ? JSON.parse(frm.doc.filters_json) : null;
frappe.xcall(
'frappe.desk.query_report.run',
{
@ -240,11 +268,14 @@ frappe.ui.form.on('Dashboard Chart', {
show_filters: function(frm) {
frm.chart_filters = [];
frappe.dashboard_utils.get_filters_for_chart_type(frm.doc).then(filters => {
if (filters) {
frm.chart_filters = filters;
}
if (filters) {
frm.chart_filters = filters;
}
frm.trigger('render_filters_table');
frm.trigger('render_filters_table');
if (frappe.boot.developer_mode && frm.doc.is_standard) {
frm.trigger('render_dynamic_filters_table');
}
});
},
@ -257,8 +288,8 @@ frappe.ui.form.on('Dashboard Chart', {
let table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
<thead>
<tr>
<th style="width: 33%">${__('Filter')}</th>
<th style="width: 33%">${__('Condition')}</th>
<th style="width: 20%">${__('Filter')}</th>
<th style="width: 20%">${__('Condition')}</th>
<th>${__('Value')}</th>
</tr>
</thead>
@ -378,4 +409,141 @@ frappe.ui.form.on('Dashboard Chart', {
});
},
render_dynamic_filters_table(frm) {
frm.set_df_property("dynamic_filters_section", "hidden", 0);
let is_document_type = frm.doc.chart_type !== 'Report'
&& frm.doc.chart_type !== 'Custom';
let wrapper = $(frm.get_field('dynamic_filters_json').wrapper).empty();
frm.dynamic_filter_table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
<thead>
<tr>
<th style="width: 20%">${__('Filter')}</th>
<th style="width: 20%">${__('Condition')}</th>
<th>${__('Value')}</th>
</tr>
</thead>
<tbody></tbody>
</table>`).appendTo(wrapper);
frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
? JSON.parse(frm.doc.dynamic_filters_json)
: null;
frm.trigger('set_dynamic_filters_in_table');
let filters = JSON.parse(frm.doc.filters_json || '[]');
let fields = [
{
fieldtype: 'HTML',
fieldname: 'description',
options:
`<div>
<p>Set dynamic filter values in JavaScript for the required fields here.
</p>
<p>Ex:
<code>frappe.defaults.get_user_default("Company")</code>
</p>
</div>`
}
];
if (is_document_type) {
if (frm.dynamic_filters) {
filters = [...filters, ...frm.dynamic_filters];
}
filters.forEach(f => {
for (let field of fields) {
if (field.fieldname == f[0] + ':' + f[1]) {
return;
}
}
if (f[2] == '=') {
fields.push({
label: `${f[1]} (${f[0]})`,
fieldname: f[0] + ':' + f[1],
fieldtype: 'Data',
});
}
});
} else {
filters = {...frm.dynamic_filters, ...filters};
for (let key of Object.keys(filters)) {
fields.push({
label: key,
fieldname: key,
fieldtype: 'Data',
});
}
}
frm.dynamic_filter_table.on('click', () => {
let dialog = new frappe.ui.Dialog({
title: __('Set Dynamic Filters'),
fields: fields,
primary_action: () => {
let values = dialog.get_values();
dialog.hide();
let dynamic_filters = [];
for (let key of Object.keys(values)) {
if (is_document_type) {
let [doctype, fieldname] = key.split(':');
dynamic_filters.push([doctype, fieldname, '=', values[key]]);
}
}
if (is_document_type) {
frm.set_value('dynamic_filters_json', JSON.stringify(dynamic_filters));
} else {
frm.set_value('dynamic_filters_json', JSON.stringify(values));
}
frm.trigger('set_dynamic_filters_in_table');
},
primary_action_label: "Set"
});
dialog.show();
dialog.set_values(frm.dynamic_filters);
});
},
set_dynamic_filters_in_table: function(frm) {
frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
? JSON.parse(frm.doc.dynamic_filters_json)
: null;
if (!frm.dynamic_filters) {
const filter_row = $(`<tr><td colspan="3" class="text-muted text-center">
${__("Click to Set Dynamic Filters")}</td></tr>`);
frm.dynamic_filter_table.find('tbody').html(filter_row);
} else {
let filter_rows = '';
if ($.isArray(frm.dynamic_filters)) {
frm.dynamic_filters.forEach(filter => {
filter_rows +=
`<tr>
<td>${filter[1]}</td>
<td>${filter[2] || ""}</td>
<td>${filter[3]}</td>
</tr>`;
});
} else {
let condition = '=';
for (let [key, val] of Object.entries(frm.dynamic_filters)) {
filter_rows +=
`<tr>
<td>${key}</td>
<td>${condition}</td>
<td>${val || ""}</td>
</tr>`
;
}
}
frm.dynamic_filter_table.find('tbody').html(filter_rows);
}
}
});

View file

@ -7,6 +7,8 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"is_standard",
"module",
"chart_name",
"chart_type",
"report_name",
@ -32,10 +34,12 @@
"type",
"filters_section",
"filters_json",
"dynamic_filters_section",
"dynamic_filters_json",
"chart_options_section",
"color",
"column_break_2",
"custom_options",
"column_break_2",
"color",
"section_break_10",
"last_synced_on"
],
@ -237,6 +241,39 @@
"fieldname": "heatmap_year",
"fieldtype": "Select",
"label": "Year"
},
{
"default": "0",
"fieldname": "is_standard",
"fieldtype": "Check",
"label": "Is Standard",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval: doc.is_standard",
"fieldname": "module",
"fieldtype": "Link",
"label": "Module",
"mandatory_depends_on": "eval: doc.is_standard",
"options": "Module Def",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dynamic_filters_json",
"fieldtype": "Code",
"label": "Dynamic Filters JSON",
"options": "JSON",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dynamic_filters_section",
"fieldtype": "Section Break",
"label": "Dynamic Filters",
"show_days": 1,
"show_seconds": 1
}
],
"links": [],

View file

@ -13,6 +13,7 @@ from frappe.utils import nowdate, add_to_date, getdate, get_last_day, formatdate
from frappe.model.naming import append_number_if_name_exists
from frappe.boot import get_allowed_reports
from frappe.model.document import Document
from frappe.modules.export_file import export_to_files
def get_permission_query_conditions(user):
@ -80,7 +81,9 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d
to_date = get_datetime(chart.to_date)
timegrain = time_interval or chart.time_interval
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json) or []
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json)
if not filters:
filters = []
# don't include cancelled documents
filters.append([chart.document_type, 'docstatus', '<', 2, False])
@ -347,8 +350,13 @@ class DashboardChart(Document):
def on_update(self):
frappe.cache().delete_key('chart-data:{}'.format(self.name))
if frappe.conf.developer_mode and self.is_standard:
export_to_files(record_list=[['Dashboard Chart', self.name]], record_module=self.module)
def validate(self):
if not frappe.conf.developer_mode and self.is_standard:
frappe.throw('Cannot edit Standard charts')
if self.chart_type != 'Custom' and self.chart_type != 'Report':
self.check_required_field()
self.check_document_type()

View file

@ -3,9 +3,32 @@
frappe.ui.form.on('Number Card', {
refresh: function(frm) {
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
frm.disable_form();
}
frm.set_df_property("filters_section", "hidden", 1);
frm.set_df_property("dynamic_filters_section", "hidden", 1);
frm.trigger('set_options');
frm.trigger('render_filters_table');
frm.trigger('render_dynamic_filters_table');
},
before_save: function(frm) {
let dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || 'null');
let static_filters = JSON.parse(frm.doc.filters_json || 'null');
static_filters =
frappe.dashboard_utils.remove_common_static_filter_values(static_filters, dynamic_filters);
frm.set_value('filters_json', JSON.stringify(static_filters));
frm.trigger('render_filters_table');
},
is_standard: function(frm) {
if (frappe.boot.developer_mode && frm.doc.is_standard) {
frm.trigger('render_dynamic_filters_table');
} else {
frm.set_df_property("dynamic_filters_section", "hidden", 1);
}
},
document_type: function(frm) {
@ -17,6 +40,7 @@ frappe.ui.form.on('Number Card', {
};
});
frm.set_value('filters_json', '[]');
frm.set_value('dynamic_filters_json', '[]');
frm.set_value('aggregate_function_based_on', '');
frm.trigger('set_options');
},
@ -47,7 +71,7 @@ frappe.ui.form.on('Number Card', {
frm.set_df_property("filters_section", "hidden", 0);
let wrapper = $(frm.get_field('filters_json').wrapper).empty();
frm.filter_table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
let table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
<thead>
<tr>
<th style="width: 33%">${__('Filter')}</th>
@ -60,9 +84,9 @@ frappe.ui.form.on('Number Card', {
frm.filters = JSON.parse(frm.doc.filters_json || '[]');
frm.trigger('set_filters_in_table');
set_filters_in_table(frm.filters, table);
frm.filter_table.on('click', () => {
table.on('click', () => {
let dialog = new frappe.ui.Dialog({
title: __('Set Filters'),
fields: [{
@ -75,7 +99,8 @@ frappe.ui.form.on('Number Card', {
this.hide();
frm.filters = frm.filter_group.get_filters();
frm.set_value('filters_json', JSON.stringify(frm.filters));
frm.trigger('set_filters_in_table');
set_filters_in_table(frm.filters, table);
frm.trigger('render_dynamic_filters_table');
}
},
primary_action_label: "Set"
@ -97,23 +122,110 @@ frappe.ui.form.on('Number Card', {
},
set_filters_in_table: function(frm) {
if (!frm.filters.length) {
const filter_row = $(`<tr><td colspan="3" class="text-muted text-center">
${__("Click to Set Filters")}</td></tr>`);
frm.filter_table.find('tbody').html(filter_row);
} else {
let filter_rows = '';
frm.filters.forEach(filter => {
filter_rows +=
`<tr>
<td>${filter[1]}</td>
<td>${filter[2] || ""}</td>
<td>${filter[3]}</td>
</tr>`;
});
frm.filter_table.find('tbody').html(filter_rows);
render_dynamic_filters_table: function(frm) {
if (!frappe.boot.developer_mode || !frm.doc.is_standard) {
return;
}
}
let wrapper = $(frm.get_field('dynamic_filters_json').wrapper).empty();
frm.set_df_property("dynamic_filters_section", "hidden", 0);
let table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
<thead>
<tr>
<th style="width: 20%">${__('Filter')}</th>
<th style="width: 20%">${__('Condition')}</th>
<th>${__('Value')}</th>
</tr>
</thead>
<tbody></tbody>
</table>`).appendTo(wrapper);
frm.dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || '[]');
set_filters_in_table(frm.dynamic_filters, table);
let filters = JSON.parse(frm.doc.filters_json || '[]');
let fields = [
{
fieldtype: 'HTML',
fieldname: 'description',
options:
`<div>
<p>Set dynamic filter values in JavaScript for the required fields here.
</p>
<p>Ex:
<code>frappe.defaults.get_user_default("Company")</code>
</p>
</div>`
}
];
if (frm.dynamic_filters.length) {
filters = [...filters, ...frm.dynamic_filters];
}
filters.forEach(f => {
for (let field of fields) {
if (field.fieldname == f[0] + ':' + f[1]) {
return;
}
}
if (f[2] == '=') {
fields.push({
label: `${f[1]} (${f[0]})`,
fieldname: f[0] + ':' + f[1],
fieldtype: 'Data',
});
}
});
table.on('click', () => {
let dialog = new frappe.ui.Dialog({
title: __('Set Filters'),
fields: fields,
primary_action: () => {
let values = dialog.get_values();
if (values) {
dialog.hide();
let dynamic_filters = [];
for (let key of Object.keys(values)) {
let [doctype, fieldname] = key.split(':');
dynamic_filters.push([doctype, fieldname, '=', values[key]]);
}
frm.set_value('dynamic_filters_json', JSON.stringify(dynamic_filters));
frm.dynamic_filters = dynamic_filters;
set_filters_in_table(frm.dynamic_filters, table);
}
},
primary_action_label: "Set"
});
dialog.show();
dialog.set_values(filters);
});
},
});
function set_filters_in_table(filters, table) {
if (!filters.length) {
const filter_row = $(`<tr><td colspan="3" class="text-muted text-center">
${__("Click to Set Filters")}</td></tr>`);
table.find('tbody').html(filter_row);
} else {
let filter_rows = '';
filters.forEach(filter => {
filter_rows +=
`<tr>
<td>${filter[1]}</td>
<td>${filter[2] || ""}</td>
<td>${filter[3]}</td>
</tr>`;
});
table.find('tbody').html(filter_rows);
}
}

View file

@ -1,10 +1,13 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2020-04-15 18:06:39.444683",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"is_standard",
"module",
"label",
"function",
"aggregate_function_based_on",
@ -16,6 +19,9 @@
"stats_time_interval",
"filters_section",
"filters_json",
"dynamic_filters_section",
"dynamic_filters_json",
"section_break_16",
"color"
],
"fields": [
@ -95,10 +101,47 @@
"fieldname": "stats_section",
"fieldtype": "Section Break",
"label": "Stats"
},
{
"default": "0",
"fieldname": "is_standard",
"fieldtype": "Check",
"label": "Is Standard"
},
{
"depends_on": "eval: doc.is_standard",
"fieldname": "module",
"fieldtype": "Link",
"label": "Module",
"mandatory_depends_on": "eval: doc.is_standard",
"options": "Module Def",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dynamic_filters_json",
"fieldtype": "Code",
"label": "Dynamic Filters JSON",
"options": "JSON",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_16",
"fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dynamic_filters_section",
"fieldtype": "Section Break",
"label": "Dynamic Filters Section",
"show_days": 1,
"show_seconds": 1
}
],
"links": [],
"modified": "2020-05-06 19:47:57.753574",
"modified": "2020-07-10 17:55:35.873222",
"modified_by": "Administrator",
"module": "Desk",
"name": "Number Card",

View file

@ -7,6 +7,7 @@ import frappe
from frappe.model.document import Document
from frappe.utils import cint
from frappe.model.naming import append_number_if_name_exists
from frappe.modules.export_file import export_to_files
class NumberCard(Document):
def autoname(self):
@ -16,6 +17,10 @@ class NumberCard(Document):
if frappe.db.exists("Number Card", self.name):
self.name = append_number_if_name_exists('Number Card', self.name)
def on_update(self):
if frappe.conf.developer_mode and self.is_standard:
export_to_files(record_list=[['Number Card', self.name]], record_module=self.module)
def get_permission_query_conditions(user=None):
if not user:
user = frappe.session.user
@ -67,6 +72,9 @@ def get_result(doc, to_date=None):
filters = frappe.parse_json(doc.filters_json)
if not filters:
filters = []
if to_date:
filters.append([doc.document_type, 'creation', '<', to_date, False])

View file

@ -7,7 +7,7 @@ from frappe.integrations.frappe_providers.frappecloud import frappecloud_migrato
def migrate_to(local_site, frappe_provider):
if frappe_provider in ("frappe.cloud", "frappecloud.com"):
return frappecloud_migrator(local_site, frappe_provider)
return frappecloud_migrator(local_site)
else:
print("{} is not supported yet".format(frappe_provider))
sys.exit(1)

View file

@ -1,412 +1,29 @@
# imports - standard imports
import getpass
import json
import os
import re
import sys
# imports - third party imports
import click
from html2text import html2text
import requests
from tenacity import retry, stop_after_attempt, wait_fixed
from html2text import html2text
# imports - module imports
import frappe
import frappe.utils.backups
from frappe.utils import get_installed_apps_info
from frappe.utils.commands import render_table, add_line_after, add_line_before
# TODO: check upgrade compatibility
def render_actions_table():
actions_table = [["#", "Action"]]
actions = []
for n, action in enumerate(migrator_actions):
actions_table.append([n+1, action["title"]])
actions.append(action["fn"])
render_table(actions_table)
return actions
def render_site_table(sites_info):
sites_table = [["#", "Site Name", "Status"]]
available_sites = []
for n, site_data in enumerate(sites_info):
name, status = site_data["name"], site_data["status"]
if status in ("Active", "Broken"):
sites_table.append([n + 1, name, status])
available_sites.append(name)
render_table(sites_table)
return available_sites
def render_teams_table(teams):
teams_table = [["#", "Team"]]
for n, team in enumerate(teams):
teams_table.append([n+1, team])
render_table(teams_table)
def render_plan_table(plans_list):
plans_table = [["Plan", "CPU Time"]]
visible_headers = ["name", "cpu_time_per_day"]
for plan in plans_list:
plan, cpu_time = [plan[header] for header in visible_headers]
plans_table.append([plan, "{} hour{}/day".format(cpu_time, "" if cpu_time < 2 else "s")])
render_table(plans_table)
def render_group_table(app_groups):
# title row
app_groups_table = [["#", "App Group", "Apps"]]
# all rows
for idx, app_group in enumerate(app_groups):
apps_list = ", ".join(["{}:{}".format(app["scrubbed"], app["branch"]) for app in app_group["apps"]])
row = [idx + 1, app_group["name"], apps_list]
app_groups_table.append(row)
render_table(app_groups_table)
def handle_request_failure(request=None, message=None, traceback=True, exit_code=1):
message = message or "Request failed with error code {}".format(request.status_code)
response = html2text(request.text) if traceback else ""
print("{0}{1}".format(message, "\n" + response))
sys.exit(exit_code)
@add_line_after
def select_primary_action():
actions = render_actions_table()
idx = click.prompt("What do you want to do?", type=click.IntRange(1, len(actions))) - 1
return actions[idx]
@add_line_after
def select_site():
get_all_sites_request = session.post(all_site_url, headers={
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"content-type": "application/json; charset=utf-8"
})
if get_all_sites_request.ok:
all_sites = get_all_sites_request.json()["message"]
available_sites = render_site_table(all_sites)
while True:
selected_site = click.prompt("Name of the site you want to restore to", type=str).strip()
if selected_site in available_sites:
return selected_site
else:
print("Site {} does not exist. Try again ❌".format(selected_site))
else:
print("Couldn't retrive sites list...Try again later")
sys.exit(1)
@add_line_before
def select_team(session):
# get team options
account_details_sc = session.post(account_details_url)
if account_details_sc.ok:
account_details = account_details_sc.json()["message"]
available_teams = account_details["teams"]
# ask if they want to select, go ahead with if only one exists
if len(available_teams) == 1:
team = available_teams[0]
else:
render_teams_table(available_teams)
idx = click.prompt("Select Team", type=click.IntRange(1, len(available_teams))) - 1
team = available_teams[idx]
print("Team '{}' set for current session".format(team))
return team
@retry(stop=stop_after_attempt(5))
def get_new_site_options():
site_options_sc = session.post(options_url)
if site_options_sc.ok:
site_options = site_options_sc.json()["message"]
return site_options
else:
print("Couldn't retrive New site information: {}".format(site_options_sc.status_code))
def is_valid_subdomain(subdomain):
if len(subdomain) < 5:
print("Subdomain too short. Use 5 or more characters")
return False
matched = re.match("^[a-z0-9][a-z0-9-]*[a-z0-9]$", subdomain)
if matched:
return True
print("Subdomain contains invalid characters. Use lowercase characters, numbers and hyphens")
@retry(stop=stop_after_attempt(5))
def is_subdomain_available(subdomain):
res = session.post(site_exists_url, {"subdomain": subdomain})
if res.ok:
available = not res.json()["message"]
if not available:
print("Subdomain already exists! Try another one")
return available
@add_line_after
def choose_plan(plans_list):
print("{} plans available".format(len(plans_list)))
available_plans = [plan["name"] for plan in plans_list]
render_plan_table(plans_list)
while True:
input_plan = click.prompt("Select Plan").strip()
if input_plan in available_plans:
print("{} Plan selected ✅".format(input_plan))
return input_plan
else:
print("Invalid Selection ❌")
@add_line_after
def check_app_compat(available_group):
is_compat = True
incompatible_apps, filtered_apps, branch_msgs = [], [], []
existing_group = [(app["app_name"], app["branch"]) for app in get_installed_apps_info()]
print("Checking availability of existing app group")
for (app, branch) in existing_group:
info = [ (a["name"], a["branch"]) for a in available_group["apps"] if a["scrubbed"] == app ]
if info:
app_title, available_branch = info[0]
if branch != available_branch:
print("⚠️ App {}:{} => {}".format(app, branch, available_branch))
branch_msgs.append([app, branch, available_branch])
filtered_apps.append(app_title)
is_compat = False
else:
print("✅ App {}:{}".format(app, branch))
filtered_apps.append(app_title)
else:
incompatible_apps.append(app)
print("❌ App {}:{}".format(app, branch))
is_compat = False
start_msg = "\nSelecting this group will "
incompatible_apps = ("\n\nDrop the following apps:\n" + "\n".join(incompatible_apps)) if incompatible_apps else ""
branch_change = ("\n\nUpgrade the following apps:\n" + "\n".join(["{}: {} => {}".format(*x) for x in branch_msgs])) if branch_msgs else ""
changes = (incompatible_apps + branch_change) or "be perfect for you :)"
warning_message = start_msg + changes
print(warning_message)
return is_compat, filtered_apps
@add_line_after
def filter_apps(app_groups):
render_group_table(app_groups)
while True:
app_group_index = click.prompt("Select App Group Number", type=int) - 1
try:
if app_group_index == -1:
raise IndexError
selected_group = app_groups[app_group_index]
except IndexError:
print("Invalid Selection ❌")
continue
is_compat, filtered_apps = check_app_compat(selected_group)
if is_compat or click.confirm("Continue anyway?"):
print("App Group {} selected! ✅".format(selected_group["name"]))
break
return selected_group["name"], filtered_apps
@add_line_after
def get_subdomain(domain):
while True:
subdomain = click.prompt("Enter subdomain").strip()
if is_valid_subdomain(subdomain) and is_subdomain_available(subdomain):
print("Site Domain: {}.{}".format(subdomain, domain))
return subdomain
@retry(stop=stop_after_attempt(2), wait=wait_fixed(5))
def upload_backup_file(file_type, file_path):
return session.post(files_url, data={}, files={
"file": open(file_path, "rb"),
"is_private": 1,
"folder": "Home",
"method": "press.api.site.upload_backup",
"type": file_type
})
@add_line_after
def upload_backup(local_site):
# take backup
files_session = {}
print("Taking backup for site {}".format(local_site))
odb = frappe.utils.backups.new_backup(ignore_files=False, force=True)
# upload files
for x, (file_type, file_path) in enumerate([
("database", odb.backup_path_db),
("public", odb.backup_path_files),
("private", odb.backup_path_private_files)
]):
file_name = file_path.split(os.sep)[-1]
print("Uploading {} file: {} ({}/3)".format(file_type, file_name, x+1))
file_upload_response = upload_backup_file(file_type, file_path)
if file_upload_response.ok:
files_session[file_type] = file_upload_response.json()["message"]
else:
print("Upload failed for: {}".format(file_path))
files_uploaded = { k: v["file_url"] for k, v in files_session.items() }
print("Uploaded backup files! ✅")
return files_uploaded
def new_site(local_site):
# get new site options
site_options = get_new_site_options()
# set preferences from site options
subdomain = get_subdomain(site_options["domain"])
plan = choose_plan(site_options["plans"])
app_groups = site_options["groups"]
selected_group, filtered_apps = filter_apps(app_groups)
files_uploaded = upload_backup(local_site)
# push to frappe_cloud
payload = json.dumps({
"site": {
"apps": filtered_apps,
"files": files_uploaded,
"group": selected_group,
"name": subdomain,
"plan": plan
}
})
session.headers.update({"Content-Type": "application/json; charset=utf-8"})
site_creation_request = session.post(upload_url, payload)
if site_creation_request.ok:
site_url = site_creation_request.json()["message"]
print("Your site {} is being migrated ✨".format(local_site))
print("View your site dashboard at {}/dashboard/#/sites/{}".format(remote_site, site_url))
print("Your site URL: {}".format(site_url))
else:
handle_request_failure(site_creation_request)
def restore_site(local_site):
# get list of existing sites they can restore
selected_site = select_site()
# TODO: check if they can restore it
click.confirm("This is an irreversible action. Are you sure you want to continue?", abort=True)
# backup site
files_uploaded = upload_backup(local_site)
# push to frappe_cloud
payload = json.dumps({
"name": selected_site,
"files": files_uploaded
})
headers = {"Content-Type": "application/json; charset=utf-8"}
site_restore_request = session.post(restore_site_url, payload, headers=headers)
if site_restore_request.ok:
print("Your site {0} is being restored on {1}".format(local_site, selected_site))
print("View your site dashboard at {}/dashboard/#/sites/{}".format(remote_site, selected_site))
print("Your site URL: {}".format(selected_site))
else:
handle_request_failure(site_restore_request)
@add_line_after
def create_session():
print("Frappe Cloud credentials @ {}".format(remote_site))
# take user input from STDIN
username = click.prompt("Username").strip()
password = getpass.unix_getpass()
auth_credentials = {"usr": username, "pwd": password}
session = requests.Session()
login_sc = session.post(login_url, auth_credentials)
if login_sc.ok:
print("Authorization Successful! ✅")
team = select_team(session)
session.headers.update({
"X-Press-Team": team,
"Connection": "keep-alive"
})
return session
else:
handle_request_failure(message="Authorization Failed with Error Code {}".format(login_sc.status_code), traceback=False)
def frappecloud_migrator(local_site, frappecloud_site):
global login_url, upload_url, files_url, options_url, site_exists_url, restore_site_url, account_details_url, all_site_url
global session, migrator_actions, remote_site
def frappecloud_migrator(local_site):
print("Retreiving Site Migrator...")
remote_site = frappe.conf.frappecloud_url or "frappecloud.com"
request_url = "https://{}/api/method/press.api.script".format(remote_site)
request = requests.get(request_url)
login_url = "https://{}/api/method/login".format(remote_site)
upload_url = "https://{}/api/method/press.api.site.new".format(remote_site)
files_url = "https://{}/api/method/upload_file".format(remote_site)
options_url = "https://{}/api/method/press.api.site.options_for_new".format(remote_site)
site_exists_url = "https://{}/api/method/press.api.site.exists".format(remote_site)
account_details_url = "https://{}/api/method/press.api.account.get".format(remote_site)
all_site_url = "https://{}/api/method/press.api.site.all".format(remote_site)
restore_site_url = "https://{}/api/method/press.api.site.restore".format(remote_site)
if request.status_code / 100 != 2:
print("Request exitted with Status Code: {}\nPayload: {}".format(request.status_code, html2text(request.text)))
click.secho("Some errors occurred while recovering the migration script. Please contact us @ Frappe Cloud if this issue persists", fg="yellow")
return
migrator_actions = [
{ "title": "Create a new site", "fn": new_site },
{ "title": "Restore to an existing site", "fn": restore_site }
]
script_contents = request.json()["message"]
# get credentials + auth user + start session
session = create_session()
import tempfile
import os
import sys
# available actions defined in migrator_actions
primary_action = select_primary_action()
primary_action(local_site)
py = sys.executable
script = tempfile.NamedTemporaryFile(mode="w")
script.write(script_contents)
print("Site Migrator stored at {}".format(script.name))
os.execv(py, [py, script.name, local_site])

View file

@ -45,7 +45,9 @@ def make_new_doc(doctype):
doc = doc.get_valid_dict(sanitize=False)
doc["doctype"] = doctype
doc["__islocal"] = 1
doc["__unsaved"] = 1
if not frappe.model.meta.is_single(doctype):
doc["__unsaved"] = 1
return doc

View file

@ -203,7 +203,7 @@ class DatabaseQuery(object):
def sanitize_fields(self):
'''
regex : ^.*[,();].*
purpose : The regex will look for malicious patterns like `,`, '(', ')', ';' in each
purpose : The regex will look for malicious patterns like `,`, '(', ')', '@', ;' in each
field which may leads to sql injection.
example :
field = "`DocType`.`issingle`, version()"
@ -211,11 +211,11 @@ class DatabaseQuery(object):
the system will filter out this field.
'''
sub_query_regex = re.compile("^.*[,();].*")
blacklisted_keywords = ['select', 'create', 'insert', 'delete', 'drop', 'update', 'case']
sub_query_regex = re.compile("^.*[,();@].*")
blacklisted_keywords = ['select', 'create', 'insert', 'delete', 'drop', 'update', 'case', 'show']
blacklisted_functions = ['concat', 'concat_ws', 'if', 'ifnull', 'nullif', 'coalesce',
'connection_id', 'current_user', 'database', 'last_insert_id', 'session_user',
'system_user', 'user', 'version']
'system_user', 'user', 'version', 'global']
def _raise_exception():
frappe.throw(_('Use of sub-query or function is restricted'), frappe.DataError)
@ -238,6 +238,10 @@ class DatabaseQuery(object):
if any("{0}(".format(keyword) in field.lower() for keyword in blacklisted_functions):
_raise_exception()
if '@' in field.lower():
# prevent access to global variables
_raise_exception()
if re.compile(r"[0-9a-zA-Z]+\s*'").match(field):
_raise_exception()
@ -854,4 +858,4 @@ def get_date_range(operator, value):
timespan = period_map[operator] + ' ' + timespan_map[value] if operator != 'timespan' else value
return get_timespan_date_range(timespan)
return get_timespan_date_range(timespan)

View file

@ -12,16 +12,17 @@ def export_doc(doc):
def export_to_files(record_list=None, record_module=None, verbose=0, create_init=None):
"""
Export record_list to files. record_list is a list of lists ([doctype],[docname] ) ,
Export record_list to files. record_list is a list of lists ([doctype, docname, folder name],) ,
"""
if frappe.flags.in_import:
return
if record_list:
for record in record_list:
write_document_file(frappe.get_doc(record[0], record[1]), record_module, create_init=create_init)
folder_name = record[2] if len(record) == 3 else None
write_document_file(frappe.get_doc(record[0], record[1]), record_module, create_init=create_init, folder_name=folder_name)
def write_document_file(doc, record_module=None, create_init=True):
def write_document_file(doc, record_module=None, create_init=True, folder_name=None):
newdoc = doc.as_dict(no_nulls=True)
doc.run_method("before_export", newdoc)
@ -35,7 +36,10 @@ def write_document_file(doc, record_module=None, create_init=True):
module = record_module or get_module_name(doc)
# create folder
folder = create_folder(module, doc.doctype, doc.name, create_init)
if folder_name:
folder = create_folder(module, folder_name, doc.name, create_init)
else:
folder = create_folder(module, doc.doctype, doc.name, create_init)
# write the data file
fname = scrub(doc.name)

View file

@ -264,7 +264,7 @@ frappe.patches.v11_0.make_all_prepared_report_attachments_private #2019-11-26
frappe.patches.v12_0.setup_email_linking
frappe.patches.v12_0.fix_home_settings_for_all_users
frappe.patches.v12_0.change_existing_dashboard_chart_filters
frappe.patches.v12_0.set_correct_assign_value_in_docs
frappe.patches.v12_0.set_correct_assign_value_in_docs #2020-07-13
execute:frappe.delete_doc("Test Runner")
execute:frappe.delete_doc_if_exists('DocType', 'Google Maps Settings')
execute:frappe.db.set_default('desktop:home_page', 'workspace')
@ -292,4 +292,5 @@ execute:frappe.delete_doc("DocType", "Onboarding Slide Field")
execute:frappe.delete_doc("DocType", "Onboarding Slide Help Link")
frappe.patches.v13_0.update_date_filters_in_user_settings
frappe.patches.v13_0.update_duration_options
frappe.patches.v13_0.replace_old_data_import # 2020-06-24
frappe.patches.v13_0.replace_old_data_import # 2020-06-24
frappe.patches.v13_0.create_custom_dashboards_cards_and_charts

View file

@ -9,8 +9,9 @@ def execute():
FROM
`tabToDo`
WHERE
COALESCE(reference_type, '') != '' and
COALESCE(reference_name, '') != ''
COALESCE(reference_type, '') != '' AND
COALESCE(reference_name, '') != '' AND
status != 'Cancelled'
GROUP BY
reference_type, reference_name
'''

View file

@ -0,0 +1,45 @@
import frappe
from frappe.model.naming import append_number_if_name_exists
from frappe.utils.dashboard import get_dashboards_with_link
def execute():
if not frappe.db.table_exists('Dashboard Chart')\
or not frappe.db.table_exists('Number Card')\
or not frappe.db.table_exists('Dashboard'):
return
frappe.reload_doc('desk', 'doctype', 'dashboard_chart')
frappe.reload_doc('desk', 'doctype', 'number_card')
frappe.reload_doc('desk', 'doctype', 'dashboard')
modified_charts = get_modified_docs('Dashboard Chart')
modified_cards = get_modified_docs('Number Card')
modified_dashboards = [doc.name for doc in get_modified_docs('Dashboard')]
for chart in modified_charts:
modified_dashboards += get_dashboards_with_link(chart.name, 'Dashboard Chart')
rename_modified_doc(chart.name, 'Dashboard Chart')
for card in modified_cards:
modified_dashboards += get_dashboards_with_link(card.name, 'Number Card')
rename_modified_doc(card.name, 'Number Card')
modified_dashboards = list(set(modified_dashboards))
for dashboard in modified_dashboards:
rename_modified_doc(dashboard, 'Dashboard')
def get_modified_docs(doctype):
return frappe.get_all(doctype,
filters = {
'owner': 'Administrator',
'modified_by': ['!=', 'Administrator']
})
def rename_modified_doc(docname, doctype):
new_name = docname + ' Custom'
try:
frappe.rename_doc(doctype, docname, new_name)
except frappe.ValidationError:
new_name = append_number_if_name_exists(doctype, new_name)
frappe.rename_doc(doctype, docname, new_name)

View file

@ -6,11 +6,15 @@ import frappe
def execute():
if not frappe.db.exists("DocType", "Data Import Beta"):
if not frappe.db.table_exists("Data Import"): return
meta = frappe.get_meta("Data Import")
# if Data Import is the new one, return early
if meta.fields[1].fieldname == "import_type":
return
frappe.db.sql("DROP TABLE IF EXISTS `tabData Import Legacy`")
frappe.rename_doc('DocType', 'Data Import', 'Data Import Legacy')
frappe.rename_doc("DocType", "Data Import", "Data Import Legacy")
frappe.db.commit()
frappe.db.sql("DROP TABLE IF EXISTS `tabData Import`")
frappe.rename_doc('DocType', 'Data Import Beta', 'Data Import')
frappe.rename_doc("DocType", "Data Import Beta", "Data Import")

View file

@ -1,932 +1,203 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"actions": [],
"creation": "2014-07-17 06:54:20.782907",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"pdf_settings",
"send_print_as_pdf",
"repeat_header_footer",
"column_break_4",
"pdf_page_size",
"view_link_in_email",
"with_letterhead",
"allow_print_for_draft",
"add_draft_heading",
"column_break_10",
"allow_page_break_inside_tables",
"allow_print_for_cancelled",
"server_printer",
"enable_print_server",
"server_ip",
"printer_name",
"port",
"raw_printing_section",
"enable_raw_printing",
"print_style_section",
"print_style",
"print_style_preview",
"section_break_8",
"font",
"font_size"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "pdf_settings",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "PDF Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "PDF Settings"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "Send Email Print Attachments as PDF (Recommended)",
"fetch_if_empty": 0,
"fieldname": "send_print_as_pdf",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Send Print as PDF",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Send Print as PDF"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "repeat_header_footer",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Repeat Header and Footer in PDF",
"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,
"translatable": 0,
"unique": 0
"label": "Repeat Header and Footer in PDF"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"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,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "A4",
"fetch_if_empty": 0,
"fieldname": "pdf_page_size",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "PDF Page Size",
"length": 0,
"no_copy": 0,
"options": "A4\nLetter",
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"options": "A4\nLetter"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "view_link_in_email",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Page Settings",
"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,
"translatable": 0,
"unique": 0
"label": "Page Settings"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "",
"fetch_if_empty": 0,
"fieldname": "with_letterhead",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print with letterhead",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Print with letterhead"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "",
"fetch_if_empty": 0,
"fieldname": "allow_print_for_draft",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Print for Draft",
"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,
"translatable": 0,
"unique": 0
"label": "Allow Print for Draft"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "",
"fetch_if_empty": 0,
"fieldname": "attach_view_link",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Send document web view link in email",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"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,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "add_draft_heading",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Always add \"Draft\" Heading for printing draft documents",
"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,
"translatable": 0,
"unique": 0
"label": "Always add \"Draft\" Heading for printing draft documents"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"default": "0",
"fieldname": "allow_page_break_inside_tables",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow page break inside tables",
"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,
"translatable": 0,
"unique": 0
"label": "Allow page break inside tables"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"default": "0",
"fieldname": "allow_print_for_cancelled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Print for Cancelled",
"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,
"translatable": 0,
"unique": 0
"label": "Allow Print for Cancelled"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "server_printer",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print Server",
"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,
"translatable": 0,
"unique": 0
"label": "Print Server"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"default": "0",
"fieldname": "enable_print_server",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enable Print Server",
"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,
"translatable": 0,
"unique": 0
"label": "Enable Print Server"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "localhost",
"depends_on": "enable_print_server",
"fetch_if_empty": 0,
"fieldname": "server_ip",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Server IP",
"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,
"translatable": 0,
"unique": 0
"label": "Server IP"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "enable_print_server",
"fetch_if_empty": 0,
"fieldname": "printer_name",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Printer Name",
"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,
"translatable": 0,
"unique": 0
"label": "Printer Name"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "631",
"depends_on": "enable_print_server",
"fetch_if_empty": 0,
"fieldname": "port",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Port",
"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,
"translatable": 0,
"unique": 0
"label": "Port"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "raw_printing_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Raw Printing",
"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,
"translatable": 0,
"unique": 0
"label": "Raw Printing"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"default": "0",
"fieldname": "enable_raw_printing",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enable Raw Printing",
"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,
"translatable": 0,
"unique": 0
"label": "Enable Raw Printing"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "print_style_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print Style",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Print Style"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Modern",
"fetch_if_empty": 0,
"fieldname": "print_style",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Print Style",
"length": 0,
"no_copy": 0,
"options": "Print Style",
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"options": "Print Style"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "print_style_preview",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print Style Preview",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Print Style Preview"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Fonts",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Fonts"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Default",
"fetch_if_empty": 0,
"fieldname": "font",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Font",
"length": 0,
"no_copy": 0,
"options": "Default\nArial\nHelvetica\nVerdana\nMonospace",
"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,
"translatable": 0,
"unique": 0
"options": "Default\nArial\nHelvetica\nVerdana\nMonospace"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "In points. Default is 9.",
"fetch_if_empty": 0,
"fieldname": "font_size",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Font Size",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Font Size"
}
],
"has_web_view": 0,
"hide_toolbar": 0,
"icon": "fa fa-cog",
"idx": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2019-04-10 14:12:31.081187",
"links": [],
"modified": "2020-07-02 16:14:47.470668",
"modified_by": "Administrator",
"module": "Printing",
"name": "Print Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}

View file

@ -101,15 +101,6 @@ frappe.Application = Class.extend({
frappe.ui.startup_setup_dialog.show();
}
// listen to csrf_update
frappe.realtime.on("csrf_generated", function(data) {
// handles the case when a user logs in again from another tab
// and it leads to invalid request in the current tab
if (data.csrf_token && data.sid===frappe.get_cookie("sid")) {
frappe.csrf_token = data.csrf_token;
}
});
frappe.realtime.on("version-update", function() {
var dialog = frappe.msgprint({
message:__("The application has been updated to a new version, please refresh this page"),

View file

@ -199,6 +199,8 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
get_input_value() {
let value = this.quill ? this.quill.root.innerHTML : '';
// hack to retain space sequence.
value = value.replace(/(\s)(\s)/g, ' &nbsp;');
return value;
},

View file

@ -842,6 +842,15 @@ frappe.ui.form.Form = class FrappeForm {
this.page.clear_primary_action();
}
disable_form() {
this.set_read_only();
this.fields
.forEach((field) => {
this.set_df_property(field.df.fieldname, "read_only", "1");
});
this.disable_save();
}
handle_save_fail(btn, on_error) {
$(btn).prop('disabled', false);
if (on_error) {
@ -1604,6 +1613,7 @@ frappe.ui.form.Form = class FrappeForm {
});
driver.defineSteps(steps);
frappe.route.on('change', () => driver.reset());
driver.start();
}
};

View file

@ -109,9 +109,10 @@ frappe.views.BaseList = class BaseList {
this.fields = this.fields.uniqBy(f => f[0] + f[1]);
}
_add_field(fieldname) {
_add_field(fieldname, doctype) {
if (!fieldname) return;
let doctype = this.doctype;
if (!doctype) doctype = this.doctype;
if (typeof fieldname === 'object') {
// df is passed
@ -120,6 +121,8 @@ frappe.views.BaseList = class BaseList {
doctype = df.parent;
}
if (!this.fields) this.fields = [];
const is_valid_field = frappe.model.std_fields_list.includes(fieldname)
|| frappe.meta.has_field(doctype, fieldname)
|| fieldname === '_seen';

View file

@ -126,7 +126,7 @@ frappe.request.call = function(opts) {
message: __('The resource you are looking for is not available')});
},
403: function(xhr) {
if (frappe.get_cookie('sid')==='Guest') {
if (frappe.session.user === 'Guest') {
// session expired
frappe.app.handle_session_expired();
}
@ -321,7 +321,7 @@ frappe.request.cleanup = function(opts, r) {
if(r) {
// session expired? - Guest has no business here!
if(r.session_expired || frappe.get_cookie("sid")==="Guest") {
if (r.session_expired || frappe.session.user === "Guest") {
frappe.app.handle_session_expired();
return;
}

View file

@ -47,7 +47,7 @@ frappe.dashboard_utils = {
frappe.dom.eval(config);
return frappe.dashboards.chart_sources[chart.source].filters;
});
} else if (chart.chart_type === 'Report') {
} else if (chart.chart_type === 'Report' && chart.report_name) {
return frappe.report_utils.get_report_filters(chart.report_name).then(filters => {
return filters;
});
@ -97,6 +97,28 @@ frappe.dashboard_utils = {
get_year(date_str) {
return date_str.substring(0, date_str.indexOf('-'));
},
remove_common_static_filter_values(static_filters, dynamic_filters) {
if (dynamic_filters) {
if ($.isArray(static_filters)) {
static_filters = static_filters.filter(static_filter => {
for (let dynamic_filter of dynamic_filters) {
if (static_filter[0] == dynamic_filter[0]
&& static_filter[1] == dynamic_filter[1]) {
return false;
}
}
return true;
});
} else {
for (let key of Object.keys(dynamic_filters)) {
delete static_filters[key];
}
}
}
return static_filters;
}
};

View file

@ -646,11 +646,13 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
set_fields() {
if (this.report_name && this.report_doc.json.fields) {
this.fields = this.report_doc.json.fields.slice();
let fields = this.report_doc.json.fields.slice();
fields.forEach(f => this._add_field(f[0], f[1]));
return;
} else if (this.view_user_settings.fields) {
// get from user_settings
this.fields = this.view_user_settings.fields;
let fields = this.view_user_settings.fields;
fields.forEach(f => this._add_field(f[0], f[1]));
return;
}

View file

@ -412,10 +412,13 @@ export default class ChartWidget extends Widget {
}
dialog.show();
//Set query report object so that it can be used while fetching filter values in the report
frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
frappe.query_reports[this.chart_doc.report_name].onload
&& frappe.query_reports[this.chart_doc.report_name].onload(frappe.query_report);
if (this.chart_doc.chart_type == 'Report') {
//Set query report object so that it can be used while fetching filter values in the report
frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
frappe.query_reports[this.chart_doc.report_name].onload
&& frappe.query_reports[this.chart_doc.report_name].onload(frappe.query_report);
}
dialog.set_values(this.filters);
}
@ -636,7 +639,7 @@ export default class ChartWidget extends Widget {
set_chart_filters() {
let user_saved_filters = this.chart_settings.filters || null;
let chart_saved_filters = JSON.parse(this.chart_doc.filters_json || "null");
let chart_saved_filters = this.get_all_chart_filters();
if (this.chart_doc.chart_type == 'Report') {
return frappe.dashboard_utils
@ -652,6 +655,38 @@ export default class ChartWidget extends Widget {
}
}
get_all_chart_filters() {
let filters = JSON.parse(this.chart_doc.filters_json || "null");
let dynamic_filters = JSON.parse(this.chart_doc.dynamic_filters_json || "null");
if (!dynamic_filters) {
return filters;
}
if ($.isArray(dynamic_filters)) {
dynamic_filters.forEach(f => {
try {
f[3] = eval(f[3]);
} catch (e) {
frappe.throw(__(`Invalid expression set in filter ${f[1]} (${f[0]})`));
}
});
filters = [...filters, ...dynamic_filters];
} else {
for (let key of Object.keys(dynamic_filters)) {
try {
const val = eval(dynamic_filters[key]);
dynamic_filters[key] = val;
} catch (e) {
frappe.throw(__(`Invalid expression set in filter ${key}`));
}
}
Object.assign(filters, dynamic_filters);
}
return filters;
}
update_default_date_filters(report_filters, chart_filters) {
report_filters.map(f => {
if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) {

View file

@ -143,6 +143,13 @@
}
}
.frappe-rtl {
.desk-body {
padding-left: 0px;
padding-right: calc(20rem + 15px);
}
}
.widget-group {
margin-bottom: 25px;
// -webkit-animation-name: slideInUp;

View file

@ -9,10 +9,6 @@
font-family: inherit;
}
.ql-editor {
white-space: normal;
}
.ql-editor {
font-family: @font-stack;
line-height: 1.6;

View file

@ -172,13 +172,6 @@ def generate_csrf_token():
frappe.local.session.data.csrf_token = frappe.generate_hash()
frappe.local.session_obj.update(force=True)
# send sid and csrf token to the user
# handles the case when a user logs in again from another tab
# and it leads to invalid request in the current tab
frappe.publish_realtime(event="csrf_generated",
message={"sid": frappe.local.session.sid, "csrf_token": frappe.local.session.data.csrf_token},
user=frappe.session.user, after_commit=True)
class Session:
def __init__(self, user, resume=False, full_name=None, user_type=None):
self.sid = cstr(frappe.form_dict.get('sid') or

View file

@ -1,7 +1,9 @@
{%- set res = frappe.utils.get_thumbnail_base64_for_image(src) if src else false -%}
{%- if res and res['base64'].startswith('data:') -%}
<img src="{{ res['base64'] }}" class="image-with-blur {{ resolve_class(class) }}"
alt="{{ alt or '' }}" width="{{ res['width'] }}" height="{{ res['height'] }}" data-src="{{ src or '' }}" />
data-src="{{ src or '' }}" alt="{{ alt or '' }}"
width="{{ res['width'] }}" height="{{ res['height'] }}"
style="width: {{ res['width'] }}px; height: {{ res['height'] }}px;" />
{%- else -%}
<img src="{{ src or '' }}" class="{{ resolve_class(class) }}" alt="{{ alt or '' }}" />
{%- endif -%}

View file

@ -167,6 +167,9 @@ class TestReportview(unittest.TestCase):
self.assertRaises(frappe.DataError, DatabaseQuery("DocType").execute,
fields=["name", "1' UNION SELECT * FROM __Auth --"],limit_start=0, limit_page_length=1)
self.assertRaises(frappe.DataError, DatabaseQuery("DocType").execute,
fields=["@@version"], limit_start=0, limit_page_length=1)
data = DatabaseQuery("DocType").execute(fields=["count(`name`) as count"],
limit_start=0, limit_page_length=1)
self.assertTrue('count' in data[0])

View file

@ -5,7 +5,7 @@ from __future__ import unicode_literals, print_function
from six.moves import input
import frappe, os, re
import frappe, os, re, git
from frappe.utils import touch_file, cstr
def make_boilerplate(dest, app_name):
@ -98,7 +98,13 @@ def make_boilerplate(dest, app_name):
with open(os.path.join(dest, hooks.app_name, hooks.app_name, "config", "docs.py"), "w") as f:
f.write(frappe.as_unicode(docs_template.format(**hooks)))
print("'{app}' created at {path}".format(app=app_name, path=os.path.join(dest, app_name)))
# initialize git repository
app_directory = os.path.join(dest, hooks.app_name)
app_repo = git.Repo.init(app_directory)
app_repo.git.add(A=True)
app_repo.index.commit("feat: Initialize App")
print("'{app}' created at {path}".format(app=app_name, path=app_directory))
manifest_template = """include MANIFEST.in

View file

@ -5,7 +5,9 @@ import frappe
from frappe import _
from functools import wraps
from frappe.utils import add_to_date, cint, get_link_to_form
from frappe.modules.import_file import import_doc
from frappe.modules.import_file import import_file_by_path
import os
from os.path import join
def cache_source(function):
@ -74,6 +76,26 @@ def get_from_date_from_timespan(to_date, timespan):
return add_to_date(to_date, years=years, months=months, days=days,
as_datetime=True)
def get_dashboards_with_link(docname, doctype):
dashboards = []
links = []
if doctype == 'Dashboard Chart':
links = frappe.get_all('Dashboard Chart Link',
fields = ['parent'],
filters = {
'chart': docname
})
elif doctype == 'Number Card':
links = frappe.get_all('Number Card Link',
fields = ['parent'],
filters = {
'card': docname
})
dashboards = [link.parent for link in links]
return dashboards
def sync_dashboards(app=None):
"""Import, overwrite fixtures from `[app]/fixtures`"""
if not cint(frappe.db.get_single_value('System Settings', 'setup_complete')):
@ -86,39 +108,23 @@ def sync_dashboards(app=None):
for app_name in apps:
print("Updating Dashboard for {app}".format(app=app_name))
for module_name in frappe.local.app_modules.get(app_name) or []:
config = get_config(app_name, module_name)
if config:
frappe.flags.in_import = True
try:
make_records(config.charts, "Dashboard Chart")
make_records(config.number_cards, "Number Card")
make_records(config.dashboards, "Dashboard")
except Exception as e:
frappe.log_error(e, _("Dashboard Import Error"))
finally:
frappe.flags.in_import = False
frappe.flags.in_import = True
make_records_in_module(app_name, module_name)
frappe.flags.in_import = False
def make_records(config, doctype):
if not config:
return
def make_records_in_module(app, module):
dashboards_path = frappe.get_module_path(module, "{module}_dashboard".format(module=module))
charts_path = frappe.get_module_path(module, "dashboard chart")
cards_path = frappe.get_module_path(module, "number card")
try:
for item in config:
item["doctype"] = doctype
import_doc(item)
frappe.db.commit()
except frappe.DuplicateEntryError:
pass
paths = [dashboards_path, charts_path, cards_path]
for path in paths:
make_records(path)
def get_config(app, module):
try:
module_dashboards = frappe.get_module('{app}.{module}.dashboard_fixtures'.format(app=app, module=module))
if hasattr(module_dashboards, 'get_data'):
return frappe._dict(module_dashboards.get_data())
return None
except ImportError:
return None
except Exception as e:
print(_("Failed to import dashboard fixtures for module {module}").format(module=module))
frappe.log_error(e, _("Dashboard Fixture Import Error"))
return None
def make_records(path, filters=None):
if os.path.isdir(path):
for fname in os.listdir(path):
if os.path.isdir(join(path, fname)):
if fname == '__pycache__':
continue
import_file_by_path("{path}/{fname}/{fname}.json".format(path=path, fname=fname))

View file

@ -137,7 +137,7 @@ def read_options_from_html(html):
except:
pass
return soup.prettify(), options
return str(soup), options
def prepare_header_footer(soup):

View file

@ -183,10 +183,6 @@ $.extend(frappe, {
.html('<div class="content"><i class="'+icon+' text-muted"></i><br>'
+text+'</div>').appendTo(document.body);
},
get_sid: function() {
var sid = frappe.get_cookie("sid");
return sid && sid !== "Guest";
},
send_message: function(opts, btn) {
return frappe.call({
type: "POST",
@ -212,8 +208,7 @@ $.extend(frappe, {
});
},
render_user: function() {
var sid = frappe.get_cookie("sid");
if(sid && sid!=="Guest") {
if (frappe.is_user_logged_in()) {
$(".btn-login-area").toggle(false);
$(".logged-in").toggle(true);
$(".full-name").html(frappe.get_cookie("full_name"));
@ -323,7 +318,7 @@ $.extend(frappe, {
return $(".navbar .search, .sidebar .search");
},
is_user_logged_in: function() {
return frappe.get_cookie("sid") && frappe.get_cookie("sid") !== "Guest";
return frappe.get_cookie("user_id") !== "Guest" && frappe.session.user !== "Guest";
},
add_switch_to_desk: function() {
$('.switch-to-desk').removeClass('hidden');

View file

@ -10,7 +10,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/website",
"idx": 0,
"is_complete": 0,
"modified": "2020-07-04 13:51:57.535269",
"modified": "2020-07-08 14:06:24.785922",
"modified_by": "Administrator",
"module": "Website",
"name": "Website",
@ -33,7 +33,6 @@
}
],
"subtitle": "Blogs, Website View Tracking, and more.",
"success_message": "Yayy! Your website is all set up!",
"title": "Let's Set Up Your Website",
"user_can_dismiss": 1
"success_message": "Your website is all set up!",
"title": "Let's Set Up Your Website."
}

View file

@ -59,7 +59,6 @@ semantic-version==2.8.4
six==1.14.0
sqlparse==0.2.4
stripe==2.40.0
tenacity==6.2.0
terminaltables==3.1.0
unittest-xml-reporting==2.5.2
urllib3==1.25.8