Merge branch 'develop' of github.com:frappe/frappe into refactor-personal-data-deletion

This commit is contained in:
Gavin D'souza 2021-03-17 13:00:30 +05:30
commit 71faba4d3f
12 changed files with 193 additions and 82 deletions

View file

@ -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) {

View file

@ -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",

View file

@ -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)

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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"""

View file

@ -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):

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
},

View file

@ -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)):