Merge branch 'develop' into notification_while_import

This commit is contained in:
Suraj Shetty 2020-06-30 18:13:46 +05:30 committed by GitHub
commit 2ce7d42e77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 241 additions and 181 deletions

View file

@ -102,6 +102,10 @@ frappe.ui.form.on('Data Import', {
},
update_primary_action(frm) {
if (frm.is_dirty()) {
frm.enable_save();
return;
}
frm.disable_save();
if (frm.doc.status !== 'Success') {
if (!frm.is_new() && (frm.has_import_file())) {
@ -199,20 +203,12 @@ frappe.ui.form.on('Data Import', {
},
download_template(frm) {
if (
frm.data_exporter &&
frm.data_exporter.doctype === frm.doc.reference_doctype
) {
frm.data_exporter.exporting_for = frm.doc.import_type;
frm.data_exporter.dialog.show();
} else {
frappe.require('/assets/js/data_import_tools.min.js', () => {
frm.data_exporter = new frappe.data_import.DataExporter(
frm.doc.reference_doctype,
frm.doc.import_type
);
});
}
frappe.require('/assets/js/data_import_tools.min.js', () => {
frm.data_exporter = new frappe.data_import.DataExporter(
frm.doc.reference_doctype,
frm.doc.import_type
);
});
},
reference_doctype(frm) {
@ -301,8 +297,8 @@ frappe.ui.form.on('Data Import', {
events: {
remap_column(changed_map) {
let template_options = JSON.parse(frm.doc.template_options || '{}');
template_options.remap_column = template_options.remap_column || {};
Object.assign(template_options.remap_column, changed_map);
template_options.column_to_field_map = template_options.column_to_field_map || {};
Object.assign(template_options.column_to_field_map, changed_map);
frm.set_value('template_options', JSON.stringify(template_options));
frm.save().then(() => frm.trigger('import_file'));
}
@ -435,10 +431,10 @@ frappe.ui.form.on('Data Import', {
.join('');
let id = frappe.dom.get_unique_id();
html = `${messages}
<button class="btn btn-default btn-xs margin-top" type="button" data-toggle="collapse" data-target="#${id}" aria-expanded="false" aria-controls="${id}">
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#${id}" aria-expanded="false" aria-controls="${id}" style="margin-top: 15px;">
${__('Show Traceback')}
</button>
<div class="collapse margin-top" id="${id}">
<div class="collapse" id="${id}" style="margin-top: 15px;">
<div class="well">
<pre>${log.exception}</pre>
</div>

View file

@ -119,7 +119,7 @@
{
"fieldname": "import_warnings_section",
"fieldtype": "Section Break",
"label": "Warnings"
"label": "Import File Errors and Warnings"
},
{
"fieldname": "import_warnings",
@ -127,7 +127,7 @@
"label": "Import Warnings"
},
{
"depends_on": "reference_doctype",
"depends_on": "eval:!doc.__islocal",
"fieldname": "download_template",
"fieldtype": "Button",
"label": "Download Template"
@ -159,7 +159,7 @@
"label": "Import from Google Sheets"
},
{
"depends_on": "eval:doc.google_sheets_url",
"depends_on": "eval:doc.google_sheets_url && !doc.__unsaved",
"fieldname": "refresh_google_sheet",
"fieldtype": "Button",
"label": "Refresh Google Sheet"
@ -167,7 +167,7 @@
],
"hide_toolbar": 1,
"links": [],
"modified": "2020-06-18 16:05:54.211034",
"modified": "2020-06-24 14:33:03.173876",
"modified_by": "Administrator",
"module": "Core",
"name": "Data Import",

View file

@ -16,6 +16,7 @@ from frappe.utils.xlsxutils import (
read_xls_file_from_attached_file,
)
from frappe.model import no_value_fields, table_fields as table_fieldtypes
from frappe.core.doctype.version.version import get_diff
INVALID_VALUES = ("", None)
MAX_ROWS_IN_PREVIEW = 10
@ -216,14 +217,22 @@ class Importer:
def update_record(self, doc):
id_field = get_id_field(self.doctype)
existing_doc = frappe.get_doc(self.doctype, doc.get(id_field.fieldname))
existing_doc.flags.updater_reference = {
"doctype": self.data_import.doctype,
"docname": self.data_import.name,
"label": _("via Data Import"),
}
existing_doc.update(doc)
existing_doc.save()
return existing_doc
updated_doc = frappe.get_doc(self.doctype, doc.get(id_field.fieldname))
updated_doc.update(doc)
if get_diff(existing_doc, updated_doc):
# update doc if there are changes
updated_doc.flags.updater_reference = {
"doctype": self.data_import.doctype,
"docname": self.data_import.name,
"label": _("via Data Import"),
}
updated_doc.save()
return updated_doc
else:
# throw if no changes
frappe.throw('No changes to update')
def get_eta(self, current, total, processing_time):
self.last_eta = getattr(self, "last_eta", 0)
@ -306,8 +315,9 @@ class ImportFile:
)
self.column_to_field_map = self.template_options.column_to_field_map
self.import_type = import_type
self.warnings = []
self.file_doc = self.file_path = None
self.file_doc = self.file_path = self.google_sheets_url = None
if isinstance(file, frappe.string_types):
if frappe.db.exists("File", {"file_url": file}):
self.file_doc = frappe.get_doc("File", {"file_url": file})
@ -462,38 +472,46 @@ class ImportFile:
parent_doc[table_df.fieldname].append(child_doc)
doc = parent_doc
# check if there is atleast one row for mandatory table fields
meta = frappe.get_meta(self.doctype)
mandatory_table_fields = [
df
for df in meta.fields
if df.fieldtype in table_fieldtypes
and df.reqd
and len(doc.get(df.fieldname, [])) == 0
]
if len(mandatory_table_fields) == 1:
self.warnings.append(
{
"row": first_row.row_number,
"message": _("There should be atleast one row for {0} table").format(
mandatory_table_fields[0].label
),
}
)
elif mandatory_table_fields:
fields_string = ", ".join([df.label for df in mandatory_table_fields])
message = _("There should be atleast one row for the following tables: {0}").format(
fields_string
)
self.warnings.append({"row": first_row.row_number, "message": message})
if self.import_type == INSERT:
# check if there is atleast one row for mandatory table fields
meta = frappe.get_meta(self.doctype)
mandatory_table_fields = [
df
for df in meta.fields
if df.fieldtype in table_fieldtypes
and df.reqd
and len(doc.get(df.fieldname, [])) == 0
]
if len(mandatory_table_fields) == 1:
self.warnings.append(
{
"row": first_row.row_number,
"message": _("There should be atleast one row for {0} table").format(
frappe.bold(mandatory_table_fields[0].label)
),
}
)
elif mandatory_table_fields:
fields_string = ", ".join([df.label for df in mandatory_table_fields])
message = _("There should be atleast one row for the following tables: {0}").format(
fields_string
)
self.warnings.append({"row": first_row.row_number, "message": message})
return doc, rows, data[len(rows) :]
def get_warnings(self):
warnings = []
# ImportFile warnings
warnings += self.warnings
# Column warnings
for col in self.header.columns:
warnings += col.warnings
# Row warnings
for row in self.data:
warnings += row.warnings
@ -607,7 +625,7 @@ class Row:
self.warnings.append(
{
"row": self.row_number,
"field": df.as_dict(convert_dates_to_str=True),
"field": df_as_json(df),
"message": msg,
}
)
@ -622,7 +640,7 @@ class Row:
self.warnings.append(
{
"row": self.row_number,
"field": df.as_dict(convert_dates_to_str=True),
"field": df_as_json(df),
"message": msg,
}
)
@ -635,7 +653,7 @@ class Row:
{
"row": self.row_number,
"col": col.column_number,
"field": df.as_dict(convert_dates_to_str=True),
"field": df_as_json(df),
"message": _("Value {0} must in {1} format").format(
frappe.bold(value), frappe.bold(get_user_format(col.date_format))
),
@ -646,7 +664,7 @@ class Row:
return value
def link_exists(self, value, df):
key = df.options + "::" + value
key = df.options + "::" + cstr(value)
if Row.link_values_exist_map.get(key) is None:
Row.link_values_exist_map[key] = frappe.db.exists(df.options, value)
return Row.link_values_exist_map.get(key)
@ -755,19 +773,21 @@ class Row:
class Header(Row):
def __init__(self, index, row, doctype, raw_data, column_to_field_map):
def __init__(self, index, row, doctype, raw_data, column_to_field_map=None):
self.index = index
self.row_number = index + 1
self.data = row
self.doctype = doctype
column_to_field_map = column_to_field_map or frappe._dict()
self.seen = []
self.columns = []
for j, header in enumerate(row):
column_values = [get_item_at_index(r, j) for r in raw_data]
map_to_field = column_to_field_map.get(str(j))
column = Column(
j, header, self.doctype, column_values, column_to_field_map.get(header), self.seen
j, header, self.doctype, column_values, map_to_field, self.seen
)
self.seen.append(header)
self.columns.append(column)
@ -824,7 +844,7 @@ class Column:
self.meta = frappe.get_meta(doctype)
self.parse()
self.parse_date_format()
self.validate_values()
def parse(self):
header_title = self.header_title
@ -897,10 +917,6 @@ class Column:
self.df = df
self.skip_import = skip_import
def parse_date_format(self):
if self.df and self.df.fieldtype in ("Date", "Time", "Datetime"):
self.date_format = self.guess_date_format_for_column()
def guess_date_format_for_column(self):
""" Guesses date format for a column by parsing all the values in the column,
getting the date format and then returning the one which has the maximum frequency
@ -935,6 +951,26 @@ class Column:
return max_occurred_date_format
def validate_values(self):
if not self.df:
return
if self.df.fieldtype == 'Link':
# find all values that dont exist
values = list(set([v for v in self.column_values[1:] if v]))
exists = [d.name for d in frappe.db.get_all(self.df.options, filters={'name': ('in', values)})]
not_exists = list(set(values) - set(exists))
if not_exists:
missing_values = ', '.join(not_exists)
self.warnings.append({
'col': self.column_number,
'message': "The following values do not exist for {}: {}".format(self.df.options, missing_values),
'type': 'warning'
})
elif self.df.fieldtype in ("Date", "Time", "Datetime"):
# guess date format
self.date_format = self.guess_date_format_for_column()
def as_dict(self):
d = frappe._dict()
d.index = self.index
@ -944,6 +980,9 @@ class Column:
d.map_to_field = self.map_to_field
d.date_format = self.date_format
d.df = self.df
if hasattr(self.df, 'is_child_table_field'):
d.is_child_table_field = self.df.is_child_table_field
d.child_table_df = self.df.child_table_df
d.skip_import = self.skip_import
d.warnings = self.warnings
return d
@ -1113,3 +1152,13 @@ def get_user_format(date_format):
.replace("%m", "mm")
.replace("%d", "dd")
)
def df_as_json(df):
return {
'fieldname': df.fieldname,
'fieldtype': df.fieldtype,
'label': df.label,
'options': df.options,
'parent': df.parent,
'default': df.default
}

View file

@ -260,9 +260,7 @@ def get_result(data, timegrain, from_date, to_date):
start_date = getdate(from_date)
end_date = getdate(to_date)
result = []
if timegrain == 'Daily':
result.append([start_date, 0.0])
result = [[start_date, 0.0]]
while start_date < end_date:
next_date = get_next_expected_date(start_date, timegrain)
@ -280,11 +278,8 @@ def get_result(data, timegrain, from_date, to_date):
def get_next_expected_date(date, timegrain):
next_date = None
if timegrain=='Daily':
next_date = add_to_date(date, days=1)
else:
# given date is always assumed to be the period ending date
next_date = get_period_ending(add_to_date(date, days=1), timegrain)
# given date is always assumed to be the period ending date
next_date = get_period_ending(add_to_date(date, days=1), timegrain)
return getdate(next_date)
def get_period_ending(date, timegrain):

View file

@ -4,13 +4,12 @@
from __future__ import unicode_literals
import unittest, frappe
from frappe.utils import getdate, formatdate
from frappe.utils import getdate, formatdate, get_last_day
from frappe.desk.doctype.dashboard_chart.dashboard_chart import (get,
get_period_ending)
from datetime import datetime
from dateutil.relativedelta import relativedelta
import calendar
class TestDashboardChart(unittest.TestCase):
def test_period_ending(self):
@ -53,16 +52,18 @@ class TestDashboardChart(unittest.TestCase):
cur_date = datetime.now() - relativedelta(years=1)
result = get(chart_name ='Test Dashboard Chart', refresh = 1)
for idx in range(13):
month = datetime(int(cur_date.year), int(cur_date.strftime('%m')), int(calendar.monthrange(cur_date.year, cur_date.month)[1]))
result = get(chart_name='Test Dashboard Chart', refresh=1)
self.assertEqual(result.get('labels')[0], formatdate(cur_date.strftime('%Y-%m-%d')))
if formatdate(cur_date.strftime('%Y-%m-%d')) == formatdate(get_last_day(cur_date).strftime('%Y-%m-%d')):
cur_date += relativedelta(months=1)
for idx in range(1, 13):
month = get_last_day(cur_date)
month = formatdate(month.strftime('%Y-%m-%d'))
self.assertEqual(result.get('labels')[idx], month)
cur_date += relativedelta(months=1)
# self.assertEqual(result.get('datasets')[0].get('values')[:-1],
# [44, 28, 8, 11, 2, 6, 18, 6, 4, 5, 15, 13])
frappe.db.rollback()
def test_empty_dashboard_chart(self):
@ -79,15 +80,20 @@ class TestDashboardChart(unittest.TestCase):
based_on = 'creation',
timespan = 'Last Year',
time_interval = 'Monthly',
filters_json = '{}',
filters_json = '[]',
timeseries = 1
)).insert()
cur_date = datetime.now() - relativedelta(years=1)
result = get(chart_name ='Test Empty Dashboard Chart', refresh = 1)
for idx in range(13):
month = datetime(int(cur_date.year), int(cur_date.strftime('%m')), int(calendar.monthrange(cur_date.year, cur_date.month)[1]))
result = get(chart_name ='Test Empty Dashboard Chart', refresh=1)
self.assertEqual(result.get('labels')[0], formatdate(cur_date.strftime('%Y-%m-%d')))
if formatdate(cur_date.strftime('%Y-%m-%d')) == formatdate(get_last_day(cur_date).strftime('%Y-%m-%d')):
cur_date += relativedelta(months=1)
for idx in range(1, 13):
month = get_last_day(cur_date)
month = formatdate(month.strftime('%Y-%m-%d'))
self.assertEqual(result.get('labels')[idx], month)
cur_date += relativedelta(months=1)
@ -111,15 +117,20 @@ class TestDashboardChart(unittest.TestCase):
based_on = 'creation',
timespan = 'Last Year',
time_interval = 'Monthly',
filters_json = '{}',
filters_json = '[]',
timeseries = 1
)).insert()
cur_date = datetime.now() - relativedelta(years=1)
result = get(chart_name ='Test Empty Dashboard Chart 2', refresh = 1)
for idx in range(13):
month = datetime(int(cur_date.year), int(cur_date.strftime('%m')), int(calendar.monthrange(cur_date.year, cur_date.month)[1]))
self.assertEqual(result.get('labels')[0], formatdate(cur_date.strftime('%Y-%m-%d')))
if formatdate(cur_date.strftime('%Y-%m-%d')) == formatdate(get_last_day(cur_date).strftime('%Y-%m-%d')):
cur_date += relativedelta(months=1)
for idx in range(1, 13):
month = get_last_day(cur_date)
month = formatdate(month.strftime('%Y-%m-%d'))
self.assertEqual(result.get('labels')[idx], month)
cur_date += relativedelta(months=1)
@ -141,7 +152,7 @@ class TestDashboardChart(unittest.TestCase):
chart_type = 'Group By',
document_type = 'ToDo',
group_by_based_on = 'status',
filters_json = '{}',
filters_json = '[]',
)).insert()
result = get(chart_name ='Test Group By Dashboard Chart', refresh = 1)
@ -168,7 +179,7 @@ class TestDashboardChart(unittest.TestCase):
time_interval = 'Daily',
from_date = datetime(2019, 1, 6),
to_date = datetime(2019, 1, 11),
filters_json = '{}',
filters_json = '[]',
timeseries = 1
)).insert()
@ -200,22 +211,24 @@ class TestDashboardChart(unittest.TestCase):
time_interval = 'Weekly',
from_date = datetime(2018, 12, 30),
to_date = datetime(2019, 1, 15),
filters_json = '{}',
filters_json = '[]',
timeseries = 1
)).insert()
result = get(chart_name ='Test Weekly Dashboard Chart', refresh = 1)
self.assertEqual(result.get('datasets')[0].get('values'), [200.0, 800.0, 0.0])
self.assertEqual(result.get('labels'), [formatdate('2019-01-06'), formatdate('2019-01-13'), formatdate('2019-01-20')])
self.assertEqual(result.get('datasets')[0].get('values'), [50.0, 300.0, 800.0, 0.0])
self.assertEqual(result.get('labels'), [formatdate('2018-12-30'), formatdate('2019-01-06'), formatdate('2019-01-13'), formatdate('2019-01-20')])
frappe.db.rollback()
def insert_test_records():
create_new_communication(datetime(2019, 1, 10), 100)
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 create_new_communication(date, rating):
communication = {

View file

@ -347,7 +347,7 @@ def flush(from_test=False):
if not smtpserver:
smtpserver = SMTPServer()
smtpserver_dict[email.sender] = smtpserver
if from_test:
send_one(email.name, smtpserver, auto_commit)
else:
@ -390,12 +390,12 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False):
where
name=%s
for update''', email, as_dict=True)
if len(email):
email = email[0]
else:
return
recipients_list = frappe.db.sql('''select name, recipient, status from
`tabEmail Queue Recipient` where parent=%s''', email.name, as_dict=1)
@ -417,6 +417,8 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False):
if email.communication:
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)
email_sent_to_any_recipient = None
try:
message = None

View file

@ -12,7 +12,7 @@ source_link = "https://github.com/frappe/frappe"
app_license = "MIT"
app_logo_url = '/assets/frappe/images/frappe-framework-logo.png'
develop_version = '12.x.x-develop'
develop_version = '13.x.x-develop'
app_email = "info@frappe.io"

View file

@ -19,6 +19,9 @@ from botocore.exceptions import ClientError
class S3BackupSettings(Document):
def validate(self):
if not self.enabled:
return
if not self.endpoint_url:
self.endpoint_url = 'https://s3.amazonaws.com'
conn = boto3.client(

View file

@ -289,4 +289,4 @@ 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
frappe.patches.v13_0.replace_old_data_import # 2020-06-24

View file

@ -6,9 +6,11 @@ import frappe
def execute():
if not frappe.db.exists("DocType", "Data Import Beta"):
return
frappe.db.sql("DROP TABLE IF EXISTS `tabData Import Legacy`")
frappe.rename_doc('DocType', 'Data Import', 'Data Import Legacy')
frappe.db.commit()
frappe.db.sql("DROP TABLE IF EXISTS `tabData Import`")
frappe.reload_doc("core", "doctype", "data_import")
frappe.get_doc("DocType", "Data Import").on_update()
frappe.delete_doc_if_exists("DocType", "Data Import Beta")
frappe.rename_doc('DocType', 'Data Import Beta', 'Data Import')

View file

@ -13,36 +13,6 @@ frappe.data_import.DataExporter = class DataExporter {
this.dialog = new frappe.ui.Dialog({
title: __('Export Data'),
fields: [
{
fieldtype: 'Select',
fieldname: 'exporting_for',
label: __('Exporting For'),
options: [
{
label: __('Insert New Records'),
value: 'Insert New Records'
},
{
label: __('Update Existing Records'),
value: 'Update Existing Records'
}
],
change: () => {
let exporting_for = this.dialog.get_value('exporting_for');
this.dialog.set_value(
'export_records',
exporting_for === 'Insert New Records' ? 'blank_template' : 'all'
);
// Force ID field to be exported when updating existing records
let id_field = this.dialog.get_field(this.doctype).options[0];
if (id_field.value === 'name' && id_field.$checkbox) {
id_field.$checkbox
.find('input')
.prop('disabled', exporting_for === 'Update Existing Records');
}
}
},
{
fieldtype: 'Select',
fieldname: 'export_records',
@ -65,7 +35,7 @@ frappe.data_import.DataExporter = class DataExporter {
value: 'blank_template'
}
],
default: 'blank_template',
default: this.exporting_for === 'Insert New Records' ? 'blank_template' : 'all',
change: () => {
this.update_record_count_message();
}
@ -119,10 +89,6 @@ frappe.data_import.DataExporter = class DataExporter {
on_page_show: () => this.select_mandatory()
});
if (this.exporting_for) {
this.dialog.set_value('exporting_for', this.exporting_for);
}
this.make_filter_area();
this.make_select_all_buttons();
this.update_record_count_message();
@ -172,15 +138,17 @@ frappe.data_import.DataExporter = class DataExporter {
}
make_select_all_buttons() {
let for_insert = this.exporting_for === 'Insert New Records';
let section_title = for_insert ? __('Select Fields To Insert') : __('Select Fields To Update');
let $select_all_buttons = $(`
<div>
<h6 class="form-section-heading uppercase">${__('Select fields to export')}</h6>
<h6 class="form-section-heading uppercase">${section_title}</h6>
<button class="btn btn-default btn-xs" data-action="select_all">
${__('Select All')}
</button>
<button class="btn btn-default btn-xs" data-action="select_mandatory">
${for_insert ? `<button class="btn btn-default btn-xs" data-action="select_mandatory">
${__('Select Mandatory')}
</button>
</button>`: ''}
<button class="btn btn-default btn-xs" data-action="unselect_all">
${__('Unselect All')}
</button>
@ -285,11 +253,9 @@ frappe.data_import.DataExporter = class DataExporter {
}
get_filters() {
return this.filter_group.get_filters().reduce((acc, filter) => {
return Object.assign(acc, {
[filter[1]]: [filter[2], filter[3]]
});
}, {});
return this.filter_group.get_filters().map(filter => {
return filter.slice(0, 4);
});
}
get_multicheck_options(doctype, child_fieldname = null) {
@ -308,6 +274,9 @@ frappe.data_import.DataExporter = class DataExporter {
? this.column_map[child_fieldname]
: this.column_map[doctype];
let is_field_mandatory = df => (df.fieldname === 'name' && !child_fieldname)
|| (df.reqd && this.exporting_for == 'Insert New Records');
return fields
.filter(df => {
if (autoname_field && df.fieldname === autoname_field.fieldname) {
@ -323,7 +292,7 @@ frappe.data_import.DataExporter = class DataExporter {
return {
label,
value: df.fieldname,
danger: df.reqd,
danger: is_field_mandatory(df),
checked: false,
description: `${df.fieldname} ${df.reqd ? __('(Mandatory)') : ''}`
};

View file

@ -245,11 +245,12 @@ frappe.data_import.ImportPreview = class ImportPreview {
let fieldname;
if (!df) {
fieldname = null;
} else if (col.map_to_field) {
fieldname = col.map_to_field;
} else if (col.is_child_table_field) {
fieldname = `${col.child_table_df.fieldname}.${df.fieldname}`;
} else {
fieldname =
df.parent === this.doctype
? df.fieldname
: `${df.parent}:${df.fieldname}`;
fieldname = df.fieldname;
}
return [
{
@ -272,7 +273,7 @@ frappe.data_import.ImportPreview = class ImportPreview {
label: __("Don't Import"),
value: "Don't Import"
}
].concat(column_picker_fields.get_fields_as_options()),
].concat(get_fields_as_options(this.doctype, column_picker_fields)),
default: fieldname || "Don't Import",
change() {
changed.push(i);
@ -328,3 +329,29 @@ frappe.data_import.ImportPreview = class ImportPreview {
});
}
};
function get_fields_as_options(doctype, column_map) {
let keys = [doctype];
frappe.meta.get_table_fields(doctype).forEach(df => {
keys.push(df.fieldname);
});
// flatten array
return [].concat(
...keys.map(key => {
return column_map[key].map(df => {
let label = df.label;
let value = df.fieldname;
if (doctype !== key) {
let table_field = frappe.meta.get_docfield(doctype, key);
label = `${df.label} (${table_field.label})`;
value = `${table_field.fieldname}.${df.fieldname}`;
}
return {
label,
value,
description: value
};
});
})
);
}

View file

@ -91,12 +91,26 @@ frappe.db = {
});
},
count: function(doctype, args={}) {
return new Promise(resolve => {
frappe.call({
method: 'frappe.client.get_count',
type: 'GET',
args: Object.assign(args, { doctype })
}).then(r => resolve(r.message));
let filters = args.filters || {};
const with_child_table_filter = Array.isArray(filters) && filters.some(filter => {
return filter[0] !== doctype;
});
const fields = [
// cannot break this line as it adds extra \n's and \t's which breaks the query
`count(${with_child_table_filter ? 'distinct': ''} ${frappe.model.get_full_column_name('name', doctype)}) AS total_count`
];
return frappe.call({
type: 'GET',
method: 'frappe.desk.reportview.get',
args: {
doctype,
filters,
fields,
}
}).then(r => {
return r.message.values[0][0];
});
},
get_link_options(doctype, txt = '', filters={}) {

View file

@ -760,26 +760,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
let current_count = this.data.length;
let count_without_children = this.data.uniqBy(d => d.name).length;
const filters = this.get_filters_for_args();
const with_child_table_filter = filters.some(filter => {
return filter[0] !== this.doctype;
});
const fields = [
// cannot break this line as it adds extra \n's and \t's which breaks the query
`count(${with_child_table_filter ? 'distinct': ''}${frappe.model.get_full_column_name('name', this.doctype)}) AS total_count`
];
return frappe.call({
type: 'GET',
method: this.method,
args: {
doctype: this.doctype,
filters,
fields,
}
}).then(r => {
this.total_count = r.message.values[0][0] || current_count;
return frappe.db.count(this.doctype, {
filters: this.get_filters_for_args()
}).then(total_count => {
this.total_count = total_count || current_count;
let str = __('{0} of {1}', [current_count, this.total_count]);
if (count_without_children !== current_count) {
str = __('{0} of {1} ({2} rows with children)', [count_without_children, this.total_count, current_count]);

View file

@ -175,12 +175,18 @@ def getlink(doctype, name):
return '<a href="#Form/%(doctype)s/%(name)s">%(name)s</a>' % locals()
def get_csv_content_from_google_sheets(url):
# https://docs.google.com/spreadsheets/d/{sheetid}}/edit#gid={gid}
validate_google_sheets_url(url)
# get gid, defaults to first sheet
if "gid=" in url:
gid = url.rsplit('gid=', 1)[1]
else:
gid = 0
# remove /edit path
url = url.rsplit('/edit', 1)[0]
# add /export path, defaults to first sheet
url = url + '/export?format=csv&gid=0'
# add /export path,
url = url + '/export?format=csv&gid={0}'.format(gid)
headers = {
'Accept': 'text/csv'
}