Merge branch 'develop' of github.com:frappe/frappe into refactor-personal-data-deletion
This commit is contained in:
commit
71faba4d3f
12 changed files with 193 additions and 82 deletions
|
|
@ -8,40 +8,42 @@ frappe.ui.form.on('Client Script', {
|
|||
() => frappe.set_route('List', frm.doc.dt, 'List'));
|
||||
}
|
||||
|
||||
frm.add_custom_button(__('Add script for Child Table'), () => {
|
||||
frappe.model.with_doctype(frm.doc.dt, () => {
|
||||
const child_tables = frappe.meta.get_docfields(frm.doc.dt, null, {
|
||||
fieldtype: 'Table'
|
||||
}).map(df => df.options);
|
||||
if (frm.doc.view == 'Form') {
|
||||
frm.add_custom_button(__('Add script for Child Table'), () => {
|
||||
frappe.model.with_doctype(frm.doc.dt, () => {
|
||||
const child_tables = frappe.meta.get_docfields(frm.doc.dt, null, {
|
||||
fieldtype: 'Table'
|
||||
}).map(df => df.options);
|
||||
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Select Child Table'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Child Table'),
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'cdt',
|
||||
options: 'DocType',
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
istable: 1,
|
||||
name: ['in', child_tables]
|
||||
}
|
||||
};
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Select Child Table'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Child Table'),
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'cdt',
|
||||
options: 'DocType',
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
istable: 1,
|
||||
name: ['in', child_tables]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
],
|
||||
primary_action: ({ cdt }) => {
|
||||
cdt = d.get_field('cdt').value;
|
||||
frm.events.add_script_for_doctype(frm, cdt);
|
||||
d.hide();
|
||||
}
|
||||
],
|
||||
primary_action: ({ cdt }) => {
|
||||
cdt = d.get_field('cdt').value;
|
||||
frm.events.add_script_for_doctype(frm, cdt);
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
d.show();
|
||||
d.show();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_query('dt', {
|
||||
filters: {
|
||||
|
|
@ -51,6 +53,8 @@ frappe.ui.form.on('Client Script', {
|
|||
},
|
||||
|
||||
dt(frm) {
|
||||
frm.toggle_display('view', !frappe.boot.single_types.includes(frm.doc.dt));
|
||||
|
||||
if (!frm.doc.script) {
|
||||
frm.events.add_script_for_doctype(frm, frm.doc.dt);
|
||||
}
|
||||
|
|
@ -61,7 +65,18 @@ frappe.ui.form.on('Client Script', {
|
|||
}
|
||||
},
|
||||
|
||||
view(frm) {
|
||||
let has_form_boilerplate = frm.doc.script.includes('frappe.ui.form.on')
|
||||
if (frm.doc.view === 'List' && has_form_boilerplate) {
|
||||
frm.set_value('script', '');
|
||||
}
|
||||
if (frm.doc.view === 'Form' && !has_form_boilerplate) {
|
||||
frm.trigger('dt');
|
||||
}
|
||||
},
|
||||
|
||||
add_script_for_doctype(frm, doctype) {
|
||||
if (!doctype) return;
|
||||
let boilerplate = `
|
||||
frappe.ui.form.on('${doctype}', {
|
||||
refresh(frm) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"dt",
|
||||
"view",
|
||||
"enabled",
|
||||
"script",
|
||||
"sample"
|
||||
|
|
@ -22,7 +23,8 @@
|
|||
"oldfieldname": "dt",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "script",
|
||||
|
|
@ -43,13 +45,21 @@
|
|||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"default": "Form",
|
||||
"fieldname": "view",
|
||||
"fieldtype": "Select",
|
||||
"label": "Apply To",
|
||||
"options": "List\nForm",
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-04 13:57:56.509437",
|
||||
"modified": "2021-03-16 20:33:51.400191",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Client Script",
|
||||
|
|
|
|||
|
|
@ -3,15 +3,29 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class ClientScript(Document):
|
||||
def autoname(self):
|
||||
self.name = self.dt
|
||||
self.name = f"{self.dt}-{self.view}"
|
||||
|
||||
def validate(self):
|
||||
if not self.is_new():
|
||||
return
|
||||
|
||||
exists = frappe.db.exists(
|
||||
"Client Script", {"dt": self.dt, "view": self.view}
|
||||
)
|
||||
if exists:
|
||||
frappe.throw(
|
||||
_("Client Script for {0} {1} already exists").format(frappe.bold(self.dt), self.view),
|
||||
frappe.DuplicateEntryError,
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
frappe.clear_cache(doctype=self.dt)
|
||||
|
||||
def on_trash(self):
|
||||
frappe.clear_cache(doctype=self.dt)
|
||||
|
||||
|
|
|
|||
|
|
@ -171,7 +171,6 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date):
|
|||
|
||||
doctype = chart.document_type
|
||||
datefield = chart.based_on
|
||||
aggregate_function = get_aggregate_function(chart.chart_type)
|
||||
value_field = chart.value_based_on or '1'
|
||||
from_date = from_date.strftime('%Y-%m-%d')
|
||||
to_date = to_date
|
||||
|
|
@ -183,7 +182,8 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date):
|
|||
doctype,
|
||||
fields = [
|
||||
'{} as _unit'.format(datefield),
|
||||
'{aggregate_function}({value_field})'.format(aggregate_function=aggregate_function, value_field=value_field),
|
||||
'SUM({})'.format(value_field),
|
||||
'COUNT(*)'
|
||||
],
|
||||
filters = filters,
|
||||
group_by = '_unit',
|
||||
|
|
@ -192,7 +192,7 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date):
|
|||
ignore_ifnull = True
|
||||
)
|
||||
|
||||
result = get_result(data, timegrain, from_date, to_date)
|
||||
result = get_result(data, timegrain, from_date, to_date, chart.chart_type)
|
||||
|
||||
chart_config = {
|
||||
"labels": [get_period(r[0], timegrain) for r in result],
|
||||
|
|
@ -288,15 +288,21 @@ def get_aggregate_function(chart_type):
|
|||
}[chart_type]
|
||||
|
||||
|
||||
def get_result(data, timegrain, from_date, to_date):
|
||||
def get_result(data, timegrain, from_date, to_date, chart_type):
|
||||
dates = get_dates_from_timegrain(from_date, to_date, timegrain)
|
||||
result = [[date, 0] for date in dates]
|
||||
data_index = 0
|
||||
if data:
|
||||
for i, d in enumerate(result):
|
||||
count = 0
|
||||
while data_index < len(data) and getdate(data[data_index][0]) <= d[0]:
|
||||
d[1] += data[data_index][1]
|
||||
count += data[data_index][2]
|
||||
data_index += 1
|
||||
if chart_type == 'Average' and not count == 0:
|
||||
d[1] = d[1]/count
|
||||
if chart_type == 'Count':
|
||||
d[1] = count
|
||||
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -212,19 +212,52 @@ class TestDashboardChart(unittest.TestCase):
|
|||
|
||||
frappe.db.rollback()
|
||||
|
||||
def insert_test_records():
|
||||
create_new_communication(datetime(2018, 12, 30), 50)
|
||||
create_new_communication(datetime(2019, 1, 4), 100)
|
||||
create_new_communication(datetime(2019, 1, 6), 200)
|
||||
create_new_communication(datetime(2019, 1, 7), 400)
|
||||
create_new_communication(datetime(2019, 1, 8), 300)
|
||||
create_new_communication(datetime(2019, 1, 10), 100)
|
||||
def test_avg_dashboard_chart(self):
|
||||
insert_test_records()
|
||||
|
||||
def create_new_communication(date, rating):
|
||||
if frappe.db.exists('Dashboard Chart', 'Test Average Dashboard Chart'):
|
||||
frappe.delete_doc('Dashboard Chart', 'Test Average Dashboard Chart')
|
||||
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Dashboard Chart',
|
||||
chart_name = 'Test Average Dashboard Chart',
|
||||
chart_type = 'Average',
|
||||
document_type = 'Communication',
|
||||
based_on = 'communication_date',
|
||||
value_based_on = 'rating',
|
||||
timespan = 'Select Date Range',
|
||||
time_interval = 'Weekly',
|
||||
from_date = datetime(2018, 12, 30),
|
||||
to_date = datetime(2019, 1, 15),
|
||||
filters_json = '[]',
|
||||
timeseries = 1
|
||||
)).insert()
|
||||
|
||||
result = get(chart_name='Test Average Dashboard Chart', refresh = 1)
|
||||
|
||||
self.assertEqual(result.get('datasets')[0].get('values'), [50.0, 150.0, 266.6666666666667, 0.0])
|
||||
self.assertEqual(
|
||||
result.get('labels'),
|
||||
['30-12-18', '06-01-19', '13-01-19', '20-01-19']
|
||||
)
|
||||
|
||||
frappe.db.rollback()
|
||||
|
||||
def insert_test_records():
|
||||
create_new_communication('Communication 1', datetime(2018, 12, 30), 50)
|
||||
create_new_communication('Communication 2', datetime(2019, 1, 4), 100)
|
||||
create_new_communication('Communication 3', datetime(2019, 1, 6), 200)
|
||||
create_new_communication('Communication 4', datetime(2019, 1, 7), 400)
|
||||
create_new_communication('Communication 5', datetime(2019, 1, 8), 300)
|
||||
create_new_communication('Communication 6', datetime(2019, 1, 10), 100)
|
||||
|
||||
def create_new_communication(subject, date, rating):
|
||||
communication = {
|
||||
'doctype': 'Communication',
|
||||
'subject': 'Test Communication',
|
||||
'subject': subject,
|
||||
'rating': rating,
|
||||
'communication_date': date
|
||||
}
|
||||
frappe.get_doc(communication).insert()
|
||||
comm = frappe.get_doc(communication)
|
||||
if not frappe.db.exists("Communication", {'subject' : comm.subject}):
|
||||
comm.insert()
|
||||
|
|
|
|||
|
|
@ -79,28 +79,30 @@ def get_submitted_linked_docs(doctype, name, docs=None, visited=None):
|
|||
@frappe.whitelist()
|
||||
def cancel_all_linked_docs(docs, ignore_doctypes_on_cancel_all=[]):
|
||||
"""
|
||||
Cancel all linked doctype
|
||||
Cancel all linked doctype, optionally ignore doctypes specified in a list.
|
||||
|
||||
Arguments:
|
||||
docs (str) - It contains all list of dictionaries of a linked documents.
|
||||
docs (json str) - It contains list of dictionaries of a linked documents.
|
||||
ignore_doctypes_on_cancel_all (list) - List of doctypes to ignore while cancelling.
|
||||
"""
|
||||
|
||||
docs = json.loads(docs)
|
||||
if isinstance(ignore_doctypes_on_cancel_all, string_types):
|
||||
ignore_doctypes_on_cancel_all = json.loads(ignore_doctypes_on_cancel_all)
|
||||
for i, doc in enumerate(docs, 1):
|
||||
if validate_linked_doc(doc, ignore_doctypes_on_cancel_all) is True:
|
||||
frappe.publish_progress(percent=i * 100 / ((len(docs) - len(ignore_doctypes_on_cancel_all))), title=_("Cancelling documents"))
|
||||
if validate_linked_doc(doc, ignore_doctypes_on_cancel_all):
|
||||
linked_doc = frappe.get_doc(doc.get("doctype"), doc.get("name"))
|
||||
linked_doc.cancel()
|
||||
frappe.publish_progress(percent=i/len(docs) * 100, title=_("Cancelling documents"))
|
||||
|
||||
|
||||
def validate_linked_doc(docinfo, ignore_doctypes_on_cancel_all=[]):
|
||||
"""
|
||||
Validate a document to be submitted and non-exempted from auto-cancel.
|
||||
|
||||
Args:
|
||||
docs (dict): The document to check for submitted and non-exempt from auto-cancel
|
||||
Arguments:
|
||||
docinfo (dict): The document to check for submitted and non-exempt from auto-cancel
|
||||
ignore_doctypes_on_cancel_all (list) - List of doctypes to ignore while cancelling.
|
||||
|
||||
Returns:
|
||||
bool: True if linked document passes all validations, else False
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class FormMeta(Meta):
|
|||
"__linked_with", "__messages", "__print_formats", "__workflow_docs",
|
||||
"__form_grid_templates", "__listview_template", "__tree_js",
|
||||
"__dashboard", "__kanban_column_fields", '__templates',
|
||||
'__custom_js'):
|
||||
'__custom_js', '__custom_list_js'):
|
||||
d[k] = self.get(k)
|
||||
|
||||
# d['fields'] = d.get('fields', [])
|
||||
|
|
@ -130,9 +130,23 @@ class FormMeta(Meta):
|
|||
def add_custom_script(self):
|
||||
"""embed all require files"""
|
||||
# custom script
|
||||
custom = frappe.db.get_value("Client Script", {"dt": self.name, "enabled": 1}, "script") or ""
|
||||
client_scripts = frappe.db.get_all("Client Script",
|
||||
filters={"dt": self.name, "enabled": 1},
|
||||
fields=["script", "view"],
|
||||
order_by="creation asc"
|
||||
) or ""
|
||||
|
||||
self.set("__custom_js", custom)
|
||||
list_script = ''
|
||||
form_script = ''
|
||||
for script in client_scripts:
|
||||
if script.view == 'List':
|
||||
list_script += script.script
|
||||
|
||||
if script.view == 'Form':
|
||||
form_script += script.script
|
||||
|
||||
self.set("__custom_js", form_script)
|
||||
self.set("__custom_list_js", list_script)
|
||||
|
||||
def add_search_fields(self):
|
||||
"""add search fields found in the doctypes indicated by link fields' options"""
|
||||
|
|
|
|||
|
|
@ -164,10 +164,14 @@ def get_script(report_name):
|
|||
module = report.module or frappe.db.get_value(
|
||||
"DocType", report.ref_doctype, "module"
|
||||
)
|
||||
module_path = get_module_path(module)
|
||||
report_folder = os.path.join(module_path, "report", scrub(report.name))
|
||||
script_path = os.path.join(report_folder, scrub(report.name) + ".js")
|
||||
print_path = os.path.join(report_folder, scrub(report.name) + ".html")
|
||||
|
||||
is_custom_module = frappe.get_cached_value("Module Def", module, "custom")
|
||||
|
||||
# custom modules are virtual modules those exists in DB but not in disk.
|
||||
module_path = '' if is_custom_module else get_module_path(module)
|
||||
report_folder = module_path and os.path.join(module_path, "report", scrub(report.name))
|
||||
script_path = report_folder and os.path.join(report_folder, scrub(report.name) + ".js")
|
||||
print_path = report_folder and os.path.join(report_folder, scrub(report.name) + ".html")
|
||||
|
||||
script = None
|
||||
if os.path.exists(script_path):
|
||||
|
|
|
|||
|
|
@ -707,25 +707,18 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
const field_html = () => {
|
||||
let html;
|
||||
let _value;
|
||||
// listview_setting formatter
|
||||
if (
|
||||
this.settings.formatters &&
|
||||
this.settings.formatters[fieldname]
|
||||
) {
|
||||
_value = this.settings.formatters[fieldname](value, df, doc);
|
||||
let strip_html_required =
|
||||
df.fieldtype == "Text Editor" ||
|
||||
(df.fetch_from &&
|
||||
["Text", "Small Text"].includes(df.fieldtype));
|
||||
|
||||
if (strip_html_required) {
|
||||
_value = strip_html(value);
|
||||
} else {
|
||||
let strip_html_required =
|
||||
df.fieldtype == "Text Editor" ||
|
||||
(df.fetch_from &&
|
||||
["Text", "Small Text"].includes(df.fieldtype));
|
||||
if (strip_html_required) {
|
||||
_value = strip_html(value);
|
||||
} else {
|
||||
_value =
|
||||
typeof value === "string"
|
||||
? frappe.utils.escape_html(value)
|
||||
: value;
|
||||
}
|
||||
_value =
|
||||
typeof value === "string"
|
||||
? frappe.utils.escape_html(value)
|
||||
: value;
|
||||
}
|
||||
|
||||
if (df.fieldtype === "Image") {
|
||||
|
|
@ -781,7 +774,15 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
Subject: this.get_subject_html(doc),
|
||||
Field: field_html(),
|
||||
};
|
||||
const column_html = html_map[col.type];
|
||||
let column_html = html_map[col.type];
|
||||
|
||||
// listview_setting formatter
|
||||
if (
|
||||
this.settings.formatters &&
|
||||
this.settings.formatters[fieldname]
|
||||
) {
|
||||
column_html = this.settings.formatters[fieldname](value, df, doc);
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="${css_class}">
|
||||
|
|
@ -912,7 +913,14 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
get_subject_html(doc) {
|
||||
let subject_field = this.columns[0].df;
|
||||
let value = doc[subject_field.fieldname] || doc.name;
|
||||
let value = doc[subject_field.fieldname];
|
||||
if (this.settings.formatters && this.settings.formatters[subject_field.fieldname]) {
|
||||
let formatter = this.settings.formatters[subject_field.fieldname];
|
||||
value = formatter(value, subject_field, doc);
|
||||
}
|
||||
if (!value) {
|
||||
value = doc.name;
|
||||
}
|
||||
let subject = strip_html(value.toString());
|
||||
let escaped_subject = frappe.utils.escape_html(subject);
|
||||
|
||||
|
|
|
|||
|
|
@ -181,6 +181,9 @@ $.extend(frappe.model, {
|
|||
if(meta.__list_js) {
|
||||
eval(meta.__list_js);
|
||||
}
|
||||
if(meta.__custom_list_js) {
|
||||
eval(meta.__custom_list_js);
|
||||
}
|
||||
if(meta.__calendar_js) {
|
||||
eval(meta.__calendar_js);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -775,8 +775,8 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
|
||||
let communication_date = last_email.communication_date || last_email.creation;
|
||||
content = `
|
||||
<div><br></div>
|
||||
${reply}
|
||||
<div><br></div>
|
||||
${frappe.separator_element || ''}
|
||||
<p>${__("On {0}, {1} wrote:", [frappe.datetime.global_date_format(communication_date) , last_email.sender])}</p>
|
||||
<blockquote>
|
||||
|
|
@ -784,7 +784,7 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
</blockquote>
|
||||
`;
|
||||
} else {
|
||||
content = "<div><br></div>" + reply;
|
||||
content = reply;
|
||||
}
|
||||
fields.content.set_value(content);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -179,6 +179,8 @@ def run_tests_for_module(module, verbose=False, tests=(), profile=False, junit_x
|
|||
return _run_unittest(module, verbose=verbose, tests=tests, profile=profile, junit_xml_output=junit_xml_output)
|
||||
|
||||
def _run_unittest(modules, verbose=False, tests=(), profile=False, junit_xml_output=False):
|
||||
frappe.db.begin()
|
||||
|
||||
test_suite = unittest.TestSuite()
|
||||
|
||||
if not isinstance(modules, (list, tuple)):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue