Merge branch 'develop' of https://github.com/frappe/frappe into custom_doctype_length
This commit is contained in:
commit
62585b3ec2
19 changed files with 220 additions and 34 deletions
8
.github/helper/roulette.py
vendored
8
.github/helper/roulette.py
vendored
|
|
@ -38,8 +38,12 @@ if __name__ == "__main__":
|
|||
pr_number = os.environ.get("PR_NUMBER")
|
||||
repo = os.environ.get("REPO_NAME")
|
||||
|
||||
if not files_list and pr_number:
|
||||
files_list = get_files_list(pr_number=pr_number, repo=repo)
|
||||
# this is a push build, run all builds
|
||||
if not pr_number:
|
||||
os.system('echo "::set-output name=build::strawberry"')
|
||||
sys.exit(0)
|
||||
|
||||
files_list = files_list or get_files_list(pr_number=pr_number, repo=repo)
|
||||
|
||||
if not files_list:
|
||||
print("No files' changes detected. Build is shutting")
|
||||
|
|
|
|||
5
.github/workflows/patch-mariadb-tests.yml
vendored
5
.github/workflows/patch-mariadb-tests.yml
vendored
|
|
@ -2,6 +2,11 @@ name: Patch
|
|||
|
||||
on: [pull_request, workflow_dispatch]
|
||||
|
||||
|
||||
concurrency:
|
||||
group: patch-mariadb-develop-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
|
|
|
|||
5
.github/workflows/server-mariadb-tests.yml
vendored
5
.github/workflows/server-mariadb-tests.yml
vendored
|
|
@ -6,6 +6,11 @@ on:
|
|||
push:
|
||||
branches: [ develop ]
|
||||
|
||||
concurrency:
|
||||
group: server-mariadb-develop-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
|
|
|
|||
4
.github/workflows/server-postgres-tests.yml
vendored
4
.github/workflows/server-postgres-tests.yml
vendored
|
|
@ -4,6 +4,10 @@ on:
|
|||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: server-postgres-develop-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
|
|
|
|||
4
.github/workflows/ui-tests.yml
vendored
4
.github/workflows/ui-tests.yml
vendored
|
|
@ -6,6 +6,10 @@ on:
|
|||
push:
|
||||
branches: [ develop ]
|
||||
|
||||
concurrency:
|
||||
group: ui-develop-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
|
|
|
|||
19
cypress/integration/datetime_field_form_validation.js
Normal file
19
cypress/integration/datetime_field_form_validation.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
context('Datetime Field Validation', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/communication');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_communication_records");
|
||||
});
|
||||
});
|
||||
|
||||
// validating datetime field value when value is set from backend and get validated on form load.
|
||||
it('datetime field form validation', () => {
|
||||
cy.visit('/app/communication');
|
||||
cy.get('a[title="Test Form Communication 1"]').invoke('attr', 'data-name')
|
||||
.then((name) => {
|
||||
cy.visit(`/app/communication/${name}`);
|
||||
cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red');
|
||||
});
|
||||
});
|
||||
});
|
||||
79
cypress/integration/folder_navigation.js
Normal file
79
cypress/integration/folder_navigation.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
context('Folder Navigation', () => {
|
||||
before(() => {
|
||||
cy.visit('/login');
|
||||
cy.login();
|
||||
cy.visit('/app/file');
|
||||
});
|
||||
|
||||
it('Adding Folders', () => {
|
||||
//Adding filter to go into the home folder
|
||||
cy.get('.filter-selector > .btn').findByText('1 filter').click();
|
||||
cy.findByRole('button', {name: 'Clear Filters'}).click();
|
||||
cy.get('.filter-action-buttons > .text-muted').findByText('+ Add a Filter').click();
|
||||
cy.get('.fieldname-select-area > .awesomplete > .form-control').type('Fol{enter}');
|
||||
cy.get('.filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback').type('Home{enter}');
|
||||
cy.get('.filter-action-buttons > div > .btn-primary').findByText('Apply Filters').click();
|
||||
|
||||
//Adding folder (Test Folder)
|
||||
cy.get('.menu-btn-group > .btn').click();
|
||||
cy.get('.menu-btn-group [data-label="New Folder"]').click();
|
||||
cy.get('form > [data-fieldname="value"]').type('Test Folder');
|
||||
cy.findByRole('button', {name: 'Create'}).click();
|
||||
});
|
||||
|
||||
it('Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct', () => {
|
||||
//Navigating inside the Attachments folder
|
||||
cy.get('[title="Attachments"] > span').click();
|
||||
|
||||
//To check if the URL formed after visiting the attachments folder is correct
|
||||
cy.location('pathname').should('eq', '/app/file/view/home/Attachments');
|
||||
cy.visit('/app/file/view/home/Attachments');
|
||||
|
||||
//Adding folder inside the attachments folder
|
||||
cy.get('.menu-btn-group > .btn').click();
|
||||
cy.get('.menu-btn-group [data-label="New Folder"]').click();
|
||||
cy.get('form > [data-fieldname="value"]').type('Test Folder');
|
||||
cy.findByRole('button', {name: 'Create'}).click();
|
||||
|
||||
//Navigating inside the added folder in the Attachments folder
|
||||
cy.get('[title="Test Folder"] > span').click();
|
||||
|
||||
//To check if the URL is correct after visiting the Test Folder
|
||||
cy.location('pathname').should('eq', '/app/file/view/home/Attachments/Test%20Folder');
|
||||
cy.visit('/app/file/view/home/Attachments/Test%20Folder');
|
||||
|
||||
//Adding a file inside the Test Folder
|
||||
cy.findByRole('button', {name: 'Add File'}).eq(0).click({force: true});
|
||||
cy.get('.file-uploader').findByText('Link').click();
|
||||
cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
|
||||
cy.findByRole('button', {name: 'Upload'}).click();
|
||||
|
||||
//To check if the added file is present in the Test Folder
|
||||
cy.get('span.level-item > span').should('contain', 'Test Folder');
|
||||
cy.get('.list-row-container').eq(0).should('contain.text', '72402.jpg');
|
||||
cy.get('.list-row-checkbox').eq(0).click();
|
||||
|
||||
//Deleting the added file from the Test folder
|
||||
cy.findByRole('button', {name: 'Actions'}).click();
|
||||
cy.get('.actions-btn-group [data-label="Delete"]').click();
|
||||
cy.wait(700);
|
||||
cy.findByRole('button', {name: 'Yes'}).click();
|
||||
cy.wait(700);
|
||||
|
||||
//Deleting the Test Folder
|
||||
cy.visit('/app/file/view/home/Attachments');
|
||||
cy.get('.list-row-checkbox').eq(0).click();
|
||||
cy.findByRole('button', {name: 'Actions'}).click();
|
||||
cy.get('.actions-btn-group [data-label="Delete"]').click();
|
||||
cy.findByRole('button', {name: 'Yes'}).click();
|
||||
});
|
||||
|
||||
it('Deleting Test Folder from the home', () => {
|
||||
//Deleting the Test Folder added in the home directory
|
||||
cy.visit('/app/file/view/home');
|
||||
cy.get('.level-left > .list-subject > .list-row-checkbox').eq(0).click({force: true, delay: 500});
|
||||
cy.findByRole('button', {name: 'Actions'}).click();
|
||||
cy.get('.actions-btn-group [data-label="Delete"]').click();
|
||||
cy.findByRole('button', {name: 'Yes'}).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -391,7 +391,7 @@ def handle_duration_fieldtype_values(result, columns):
|
|||
return result
|
||||
|
||||
|
||||
def build_xlsx_data(columns, data, visible_idx, include_indentation):
|
||||
def build_xlsx_data(columns, data, visible_idx, include_indentation, ignore_visible_idx=False):
|
||||
result = [[]]
|
||||
column_widths = []
|
||||
|
||||
|
|
@ -407,7 +407,7 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation):
|
|||
# build table from result
|
||||
for row_idx, row in enumerate(data.result):
|
||||
# only pick up rows that are visible in the report
|
||||
if row_idx in visible_idx:
|
||||
if ignore_visible_idx or row_idx in visible_idx:
|
||||
row_data = []
|
||||
if isinstance(row, dict):
|
||||
for col_idx, column in enumerate(data.columns):
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from frappe.utils import (format_time, get_link_to_form, get_url_to_report,
|
|||
from frappe.model.naming import append_number_if_name_exists
|
||||
from frappe.utils.csvutils import to_csv
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
from frappe.desk.query_report import build_xlsx_data
|
||||
|
||||
max_reports_per_user = frappe.local.conf.max_reports_per_user or 3
|
||||
|
||||
|
|
@ -99,13 +100,21 @@ class AutoEmailReport(Document):
|
|||
return self.get_html_table(columns, data)
|
||||
|
||||
elif self.format == 'XLSX':
|
||||
spreadsheet_data = self.get_spreadsheet_data(columns, data)
|
||||
xlsx_file = make_xlsx(spreadsheet_data, "Auto Email Report")
|
||||
report_data = frappe._dict()
|
||||
report_data['columns'] = columns
|
||||
report_data['result'] = data
|
||||
|
||||
xlsx_data, column_widths = build_xlsx_data(columns, report_data, [], 1, ignore_visible_idx=True)
|
||||
xlsx_file = make_xlsx(xlsx_data, "Auto Email Report", column_widths=column_widths)
|
||||
return xlsx_file.getvalue()
|
||||
|
||||
elif self.format == 'CSV':
|
||||
spreadsheet_data = self.get_spreadsheet_data(columns, data)
|
||||
return to_csv(spreadsheet_data)
|
||||
report_data = frappe._dict()
|
||||
report_data['columns'] = columns
|
||||
report_data['result'] = data
|
||||
|
||||
xlsx_data, column_widths = build_xlsx_data(columns, report_data, [], 1, ignore_visible_idx=True)
|
||||
return to_csv(xlsx_data)
|
||||
|
||||
else:
|
||||
frappe.throw(_('Invalid Output Format'))
|
||||
|
|
@ -126,18 +135,6 @@ class AutoEmailReport(Document):
|
|||
'edit_report_settings': get_link_to_form('Auto Email Report', self.name)
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def get_spreadsheet_data(columns, data):
|
||||
out = [[_(df.label) for df in columns], ]
|
||||
for row in data:
|
||||
new_row = []
|
||||
out.append(new_row)
|
||||
for df in columns:
|
||||
if df.fieldname not in row: continue
|
||||
new_row.append(frappe.format(row[df.fieldname], df, row))
|
||||
|
||||
return out
|
||||
|
||||
def get_file_name(self):
|
||||
return "{0}.{1}".format(self.report.replace(" ", "-").replace("/", "-"), self.format.lower())
|
||||
|
||||
|
|
|
|||
|
|
@ -874,7 +874,7 @@ class BaseDocument(object):
|
|||
return self._precision[cache_key][fieldname]
|
||||
|
||||
|
||||
def get_formatted(self, fieldname, doc=None, currency=None, absolute_value=False, translated=False):
|
||||
def get_formatted(self, fieldname, doc=None, currency=None, absolute_value=False, translated=False, format=None):
|
||||
from frappe.utils.formatters import format_value
|
||||
|
||||
df = self.meta.get_field(fieldname)
|
||||
|
|
@ -898,7 +898,7 @@ class BaseDocument(object):
|
|||
if (absolute_value or doc.get('absolute_value')) and isinstance(val, (int, float)):
|
||||
val = abs(self.get(fieldname))
|
||||
|
||||
return format_value(val, df=df, doc=doc, currency=currency)
|
||||
return format_value(val, df=df, doc=doc, currency=currency, format=format)
|
||||
|
||||
def is_print_hide(self, fieldname, df=None, for_print=True):
|
||||
"""Returns true if fieldname is to be hidden for print.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ frappe.data_import.DataExporter = class DataExporter {
|
|||
this.dialog = new frappe.ui.Dialog({
|
||||
title: __('Export Data'),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'file_type',
|
||||
label: __('File Type'),
|
||||
options: ['Excel', 'CSV'],
|
||||
default: 'CSV'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'export_records',
|
||||
|
|
@ -45,13 +52,6 @@ frappe.data_import.DataExporter = class DataExporter {
|
|||
fieldname: 'filter_area',
|
||||
depends_on: doc => doc.export_records === 'by_filter'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'file_type',
|
||||
label: __('File Type'),
|
||||
options: ['Excel', 'CSV'],
|
||||
default: 'CSV'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Section Break'
|
||||
},
|
||||
|
|
@ -141,7 +141,7 @@ frappe.data_import.DataExporter = class DataExporter {
|
|||
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>
|
||||
<div class="mb-3">
|
||||
<h6 class="form-section-heading uppercase">${section_title}</h6>
|
||||
<button class="btn btn-default btn-xs" data-action="select_all">
|
||||
${__('Select All')}
|
||||
|
|
|
|||
|
|
@ -36,4 +36,9 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co
|
|||
$tp.$secondsText.prev().css('display', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
get_model_value() {
|
||||
let value = super.get_model_value();
|
||||
return frappe.datetime.get_datetime_as_string(value);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -261,4 +261,14 @@ export default class BulkOperations {
|
|||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
export(doctype, docnames) {
|
||||
frappe.require('data_import_tools.bundle.js', () => {
|
||||
const data_exporter = new frappe.data_import.DataExporter(doctype, 'Insert New Records');
|
||||
data_exporter.dialog.set_value('export_records', 'by_filter');
|
||||
data_exporter.filter_group.add_filters_to_filter_group(
|
||||
[[doctype, "name", "in", docnames, false]]
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1732,11 +1732,25 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
};
|
||||
};
|
||||
|
||||
const bulk_export = () => {
|
||||
return {
|
||||
label: __("Export"),
|
||||
action: () => {
|
||||
const docnames = this.get_checked_items(true);
|
||||
|
||||
bulk_operations.export(doctype, docnames);
|
||||
},
|
||||
standard: true
|
||||
};
|
||||
};
|
||||
|
||||
// bulk edit
|
||||
if (has_editable_fields(doctype)) {
|
||||
actions_menu_items.push(bulk_edit());
|
||||
}
|
||||
|
||||
actions_menu_items.push(bulk_export());
|
||||
|
||||
// bulk assignment
|
||||
actions_menu_items.push(bulk_assignment());
|
||||
|
||||
|
|
|
|||
|
|
@ -227,3 +227,28 @@ class TestDocument(unittest.TestCase):
|
|||
self.assertEqual(frappe.db.get_value("Currency", d.name), d.name)
|
||||
|
||||
frappe.delete_doc_if_exists("Currency", "Frappe Coin", 1)
|
||||
|
||||
def test_get_formatted(self):
|
||||
frappe.get_doc({
|
||||
'doctype': 'DocType',
|
||||
'name': 'Test Formatted',
|
||||
'module': 'Custom',
|
||||
'custom': 1,
|
||||
'fields': [
|
||||
{'label': 'Currency', 'fieldname': 'currency', 'reqd': 1, 'fieldtype': 'Currency'},
|
||||
]
|
||||
}).insert()
|
||||
|
||||
frappe.delete_doc_if_exists("Currency", "INR", 1)
|
||||
|
||||
d = frappe.get_doc({
|
||||
'doctype': 'Currency',
|
||||
'currency_name': 'INR',
|
||||
'symbol': '₹',
|
||||
}).insert()
|
||||
|
||||
d = frappe.get_doc({
|
||||
'doctype': 'Test Formatted',
|
||||
'currency': 100000
|
||||
})
|
||||
self.assertEquals(d.get_formatted('currency', currency='INR', format="#,###.##"), '₹ 100,000.00')
|
||||
|
|
@ -92,6 +92,9 @@ class TestFmtMoney(unittest.TestCase):
|
|||
self.assertEqual(fmt_money(1000.456), "1.000,456")
|
||||
frappe.db.set_default("currency_precision", "")
|
||||
|
||||
def test_custom_fmt_money_format(self):
|
||||
self.assertEqual(fmt_money(100000, format="#,###.##"), '100,000.00')
|
||||
|
||||
if __name__=="__main__":
|
||||
frappe.connect()
|
||||
unittest.main()
|
||||
|
|
@ -17,9 +17,9 @@ class TestFormatter(unittest.TestCase):
|
|||
frappe.db.set_default("currency", 'INR')
|
||||
|
||||
# if currency field is not passed then default currency should be used.
|
||||
self.assertEqual(format(100, df, doc), '₹ 100.00')
|
||||
self.assertEqual(format(100000, df, doc, format="#,###.##"), '₹ 100,000.00')
|
||||
|
||||
doc.currency = 'USD'
|
||||
self.assertEqual(format(100, df, doc), "$ 100.00")
|
||||
self.assertEqual(format(100000, df, doc, format="#,###.##"), "$ 100,000.00")
|
||||
|
||||
frappe.db.set_default("currency", None)
|
||||
|
|
@ -61,6 +61,18 @@ def create_todo_records():
|
|||
"description": "this is fourth todo"
|
||||
}).insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_communication_records():
|
||||
if frappe.db.get_all('Communication', {'subject': 'Test Form Communication 1'}):
|
||||
return
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"recipients": "test@gmail.com",
|
||||
"subject": "Test Form Communication 1",
|
||||
"communication_date": frappe.utils.now_datetime(),
|
||||
}).insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
def setup_workflow():
|
||||
from frappe.workflow.doctype.workflow.test_workflow import create_todo_workflow
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime
|
|||
from frappe.model.meta import get_field_currency, get_field_precision
|
||||
import re
|
||||
|
||||
def format_value(value, df=None, doc=None, currency=None, translated=False):
|
||||
def format_value(value, df=None, doc=None, currency=None, translated=False, format=None):
|
||||
'''Format value based on given fieldtype, document reference, currency reference.
|
||||
If docfield info (df) is not given, it will try and guess based on the datatype of the value'''
|
||||
if isinstance(df, str):
|
||||
|
|
@ -56,7 +56,7 @@ def format_value(value, df=None, doc=None, currency=None, translated=False):
|
|||
elif df.get("fieldtype") == "Currency":
|
||||
default_currency = frappe.db.get_default("currency")
|
||||
currency = currency or get_field_currency(df, doc) or default_currency
|
||||
return fmt_money(value, precision=get_field_precision(df, doc), currency=currency)
|
||||
return fmt_money(value, precision=get_field_precision(df, doc), currency=currency, format=format)
|
||||
|
||||
elif df.get("fieldtype") == "Float":
|
||||
precision = get_field_precision(df, doc)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue