Merge branch 'develop' into ci_ubuntu_latest
This commit is contained in:
commit
049df53cd8
43 changed files with 380 additions and 116 deletions
|
|
@ -1,9 +1,13 @@
|
|||
codecov:
|
||||
require_ci_to_pass: yes
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0.5%
|
||||
|
||||
comment:
|
||||
layout: "diff, flags, files"
|
||||
layout: "diff"
|
||||
require_changes: true
|
||||
|
|
|
|||
|
|
@ -31,8 +31,13 @@ context('API Resources', () => {
|
|||
});
|
||||
|
||||
it('Removes the Comments', () => {
|
||||
cy.get_list('Comment').then(body => body.data.forEach(comment => {
|
||||
cy.remove_doc('Comment', comment.name);
|
||||
}));
|
||||
cy.get_list('Comment').then(body => {
|
||||
let comment_names = [];
|
||||
body.data.map(comment => comment_names.push(comment.name));
|
||||
comment_names = [...new Set(comment_names)]; // remove duplicates
|
||||
comment_names.forEach((comment_name) => {
|
||||
cy.remove_doc('Comment', comment_name);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
93
cypress/integration/control_float.js
Normal file
93
cypress/integration/control_float.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
context("Control Float", () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit("/app/website");
|
||||
});
|
||||
|
||||
function get_dialog_with_float() {
|
||||
return cy.dialog({
|
||||
title: "Float Check",
|
||||
fields: [
|
||||
{
|
||||
fieldname: "float_number",
|
||||
fieldtype: "Float",
|
||||
Label: "Float"
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
it("check value changes", () => {
|
||||
get_dialog_with_float().as("dialog");
|
||||
|
||||
let data = get_data();
|
||||
data.forEach(x => {
|
||||
cy.window()
|
||||
.its("frappe")
|
||||
.then(frappe => {
|
||||
frappe.boot.sysdefaults.number_format = x.number_format;
|
||||
});
|
||||
x.values.forEach(d => {
|
||||
cy.get_field("float_number", "Float").clear();
|
||||
cy.fill_field("float_number", d.input, "Float").blur();
|
||||
cy.get_field("float_number", "Float").should(
|
||||
"have.value",
|
||||
d.blur_expected
|
||||
);
|
||||
|
||||
cy.get_field("float_number", "Float").focus();
|
||||
cy.get_field("float_number", "Float").blur();
|
||||
cy.get_field("float_number", "Float").focus();
|
||||
cy.get_field("float_number", "Float").should(
|
||||
"have.value",
|
||||
d.focus_expected
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function get_data() {
|
||||
return [
|
||||
{
|
||||
number_format: "#.###,##",
|
||||
values: [
|
||||
{
|
||||
input: "364.87,334",
|
||||
blur_expected: "36.487,334",
|
||||
focus_expected: "36487.334"
|
||||
},
|
||||
{
|
||||
input: "36487,334",
|
||||
blur_expected: "36.487,334",
|
||||
focus_expected: "36487.334"
|
||||
},
|
||||
{
|
||||
input: "100",
|
||||
blur_expected: "100,000",
|
||||
focus_expected: "100"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
number_format: "#,###.##",
|
||||
values: [
|
||||
{
|
||||
input: "364,87.334",
|
||||
blur_expected: "36,487.334",
|
||||
focus_expected: "36487.334"
|
||||
},
|
||||
{
|
||||
input: "36487.334",
|
||||
blur_expected: "36,487.334",
|
||||
focus_expected: "36487.334"
|
||||
},
|
||||
{
|
||||
input: "100",
|
||||
blur_expected: "100.000",
|
||||
focus_expected: "100"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
|
|
@ -1,19 +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");
|
||||
});
|
||||
});
|
||||
// TODO: Enable this again
|
||||
// currently this is flaky possibly because of different timezone in CI
|
||||
|
||||
// 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
// context('Datetime Field Validation', () => {
|
||||
// before(() => {
|
||||
// cy.login();
|
||||
// cy.visit('/app/communication');
|
||||
// });
|
||||
|
||||
// it('datetime field form validation', () => {
|
||||
// // validating datetime field value when value is set from backend and get validated on form load.
|
||||
// cy.window().its('frappe').then(frappe => {
|
||||
// return frappe.xcall("frappe.tests.ui_test_helpers.create_communication_record");
|
||||
// }).then(doc => {
|
||||
// cy.visit(`/app/communication/${doc.name}`);
|
||||
// cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red');
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
|
@ -7,11 +7,11 @@ context('List View', () => {
|
|||
});
|
||||
});
|
||||
it('enables "Actions" button', () => {
|
||||
const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete'];
|
||||
const actions = ['Approve', 'Reject', 'Edit', 'Export', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete'];
|
||||
cy.go_to_list('ToDo');
|
||||
cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ multiple: true, force: true });
|
||||
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click();
|
||||
cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 8).each((el, index) => {
|
||||
cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 9).each((el, index) => {
|
||||
cy.wrap(el).contains(actions[index]);
|
||||
}).then((elements) => {
|
||||
cy.intercept({
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ context('Sidebar', () => {
|
|||
});
|
||||
|
||||
it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => {
|
||||
cy.click_sidebar_button(0);
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
|
||||
//To check if no filter is available in "Assigned To" dropdown
|
||||
cy.get('.empty-state').should('contain', 'No filters found');
|
||||
|
||||
cy.click_sidebar_button(1);
|
||||
cy.click_sidebar_button("Created By");
|
||||
|
||||
//To check if "Created By" dropdown contains filter
|
||||
cy.get('.group-by-item > .dropdown-item').should('contain', 'Me');
|
||||
|
|
@ -22,7 +22,7 @@ context('Sidebar', () => {
|
|||
cy.get_field('assign_to_me', 'Check').click();
|
||||
cy.get('.modal-footer > .standard-actions > .btn-primary').click();
|
||||
cy.visit('/app/doctype');
|
||||
cy.click_sidebar_button(0);
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
|
||||
//To check if filter is added in "Assigned To" dropdown after assignment
|
||||
cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').should('contain', '1');
|
||||
|
|
@ -38,20 +38,19 @@ context('Sidebar', () => {
|
|||
cy.get('.fieldname-select-area > .awesomplete > .form-control').should('have.value', 'Assigned To');
|
||||
cy.get('.condition').should('have.value', 'like');
|
||||
cy.get('.filter-field > .form-group > .input-with-feedback').should('have.value', '%Administrator%');
|
||||
cy.click_filter_button();
|
||||
|
||||
//To remove the applied filter
|
||||
cy.get('.filter-action-buttons > div > .btn-secondary').contains('Clear Filters').click();
|
||||
cy.click_filter_button();
|
||||
cy.get('.filter-selector > .btn').should('contain', 'Filter');
|
||||
cy.clear_filters();
|
||||
|
||||
//To remove the assignment
|
||||
cy.visit('/app/doctype');
|
||||
cy.click_listview_row_item(0);
|
||||
cy.get('.assignments > .avatar-group > .avatar > .avatar-frame').click();
|
||||
cy.get('.remove-btn').click({force: true});
|
||||
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-header > .modal-actions > .btn-modal-close').click();
|
||||
cy.hide_dialog();
|
||||
cy.visit('/app/doctype');
|
||||
cy.click_sidebar_button(0);
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
cy.get('.empty-state').should('contain', 'No filters found');
|
||||
});
|
||||
});
|
||||
|
|
@ -4,11 +4,11 @@ context('Timeline', () => {
|
|||
before(() => {
|
||||
cy.visit('/login');
|
||||
cy.login();
|
||||
cy.visit('/app/todo');
|
||||
});
|
||||
|
||||
it('Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo', () => {
|
||||
//Adding new ToDo
|
||||
cy.visit('/app/todo');
|
||||
cy.click_listview_primary_button('Add ToDo');
|
||||
cy.findByRole('button', {name: 'Edit in full page'}).click();
|
||||
cy.get('[data-fieldname="description"] .ql-editor').eq(0).type('Test ToDo', {force: true});
|
||||
|
|
@ -28,15 +28,15 @@ context('Timeline', () => {
|
|||
cy.get('.timeline-content').should('contain', 'Testing Timeline');
|
||||
|
||||
//Editing comment
|
||||
cy.click_timeline_action_btn(0);
|
||||
cy.click_timeline_action_btn("Edit");
|
||||
cy.get('.timeline-content [data-fieldname="comment"] .ql-editor').first().type(' 123');
|
||||
cy.click_timeline_action_btn(0);
|
||||
cy.click_timeline_action_btn("Save");
|
||||
|
||||
//To check if the edited comment text is visible in timeline content
|
||||
cy.get('.timeline-content').should('contain', 'Testing Timeline 123');
|
||||
|
||||
//Discarding comment
|
||||
cy.click_timeline_action_btn(0);
|
||||
cy.click_timeline_action_btn("Edit");
|
||||
cy.findByRole('button', {name: 'Dismiss'}).click();
|
||||
|
||||
//To check if after discarding the timeline content is same as previous
|
||||
|
|
@ -81,7 +81,7 @@ context('Timeline', () => {
|
|||
cy.visit('/app/custom-submittable-doctype');
|
||||
cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click();
|
||||
cy.findByRole('button', {name: 'Actions'}).click();
|
||||
cy.get('.actions-btn-group > .dropdown-menu > li > .grey-link').eq(7).click();
|
||||
cy.get('.actions-btn-group > .dropdown-menu > li > .dropdown-item').contains("Delete").click();
|
||||
cy.click_modal_primary_button('Yes', {force: true, delay: 700});
|
||||
|
||||
//Deleting the custom doctype
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
|
|||
if (fieldtype === 'Select') {
|
||||
cy.get('@input').select(value);
|
||||
} else {
|
||||
cy.get('@input').type(value, {waitForAnimations: false, force: true});
|
||||
cy.get('@input').type(value, {waitForAnimations: false, force: true, delay: 100});
|
||||
}
|
||||
return cy.get('@input');
|
||||
});
|
||||
|
|
@ -252,7 +252,8 @@ Cypress.Commands.add('new_form', doctype => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('go_to_list', doctype => {
|
||||
cy.visit(`/app/list/${doctype}/list`);
|
||||
let dt_in_route = doctype.toLowerCase().replace(/ /g, '-');
|
||||
cy.visit(`/app/${dt_in_route}`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('clear_cache', () => {
|
||||
|
|
@ -316,7 +317,11 @@ Cypress.Commands.add('add_filter', () => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('clear_filters', () => {
|
||||
cy.get('.filter-section .filter-button').click();
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: 'api/method/frappe.model.utils.user_settings.save'
|
||||
}).as('filter-saved');
|
||||
cy.get('.filter-section .filter-button').click({force: true});
|
||||
cy.wait(300);
|
||||
cy.get('.filter-popover').should('exist');
|
||||
cy.get('.filter-popover').find('.clear-filters').click();
|
||||
|
|
@ -324,16 +329,15 @@ Cypress.Commands.add('clear_filters', () => {
|
|||
cy.window().its('cur_list').then(cur_list => {
|
||||
cur_list && cur_list.filter_area && cur_list.filter_area.clear();
|
||||
});
|
||||
|
||||
|
||||
cy.wait('@filter-saved');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('click_modal_primary_button', (btn_name) => {
|
||||
cy.get('.modal-footer > .standard-actions > .btn-primary').contains(btn_name).trigger('click', {force: true});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('click_sidebar_button', (btn_no) => {
|
||||
cy.get('.list-group-by-fields > .group-by-field > .btn').eq(btn_no).click();
|
||||
Cypress.Commands.add('click_sidebar_button', (btn_name) => {
|
||||
cy.get('.list-group-by-fields .list-link > a').contains(btn_name).click({force: true});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('click_listview_row_item', (row_no) => {
|
||||
|
|
@ -348,6 +352,6 @@ Cypress.Commands.add('click_listview_primary_button', (btn_name) => {
|
|||
cy.get('.primary-action').contains(btn_name).click({force: true});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('click_timeline_action_btn', (btn_no) => {
|
||||
cy.get('.timeline-content > .timeline-message-box > .justify-between > .actions > .btn').eq(btn_no).first().click();
|
||||
Cypress.Commands.add('click_timeline_action_btn', (btn_name) => {
|
||||
cy.get('.timeline-content > .timeline-message-box > .justify-between > .actions > .btn').contains(btn_name).click();
|
||||
});
|
||||
|
|
@ -618,8 +618,6 @@ def read_only():
|
|||
|
||||
try:
|
||||
retval = fn(*args, **get_newargs(fn, kwargs))
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
if local and hasattr(local, 'primary_db'):
|
||||
local.db.close()
|
||||
|
|
@ -629,6 +627,29 @@ def read_only():
|
|||
return wrapper_fn
|
||||
return innfn
|
||||
|
||||
def write_only():
|
||||
# if replica connection exists, we have to replace it momentarily with the primary connection
|
||||
def innfn(fn):
|
||||
def wrapper_fn(*args, **kwargs):
|
||||
primary_db = getattr(local, "primary_db", None)
|
||||
replica_db = getattr(local, "replica_db", None)
|
||||
in_read_only = getattr(local, "db", None) != primary_db
|
||||
|
||||
# switch to primary connection
|
||||
if in_read_only and primary_db:
|
||||
local.db = local.primary_db
|
||||
|
||||
try:
|
||||
retval = fn(*args, **get_newargs(fn, kwargs))
|
||||
finally:
|
||||
# switch back to replica connection
|
||||
if in_read_only and replica_db:
|
||||
local.db = replica_db
|
||||
|
||||
return retval
|
||||
return wrapper_fn
|
||||
return innfn
|
||||
|
||||
def only_for(roles, message=False):
|
||||
"""Raise `frappe.PermissionError` if the user does not have any of the given **Roles**.
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class Address(Document):
|
|||
|
||||
def has_link(self, doctype, name):
|
||||
for link in self.links:
|
||||
if link.link_doctype==doctype and link.link_name== name:
|
||||
if link.link_doctype == doctype and link.link_name == name:
|
||||
return True
|
||||
|
||||
def has_common_link(self, doc):
|
||||
|
|
|
|||
|
|
@ -47,14 +47,14 @@ class Contact(Document):
|
|||
def get_link_for(self, link_doctype):
|
||||
'''Return the link name, if exists for the given link DocType'''
|
||||
for link in self.links:
|
||||
if link.link_doctype==link_doctype:
|
||||
if link.link_doctype == link_doctype:
|
||||
return link.link_name
|
||||
|
||||
return None
|
||||
|
||||
def has_link(self, doctype, name):
|
||||
for link in self.links:
|
||||
if link.link_doctype==doctype and link.link_name== name:
|
||||
if link.link_doctype == doctype and link.link_name == name:
|
||||
return True
|
||||
|
||||
def has_common_link(self, doc):
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class AccessLog(Document):
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.write_only()
|
||||
def make_access_log(doctype=None, document=None, method=None, file_type=None,
|
||||
report_name=None, filters=None, page=None, columns=None):
|
||||
|
||||
|
|
|
|||
|
|
@ -36,8 +36,11 @@ class UserType(Document):
|
|||
if not self.user_doctypes:
|
||||
return
|
||||
|
||||
modules = frappe.get_all('DocType', fields=['distinct module as module'],
|
||||
filters={'name': ('in', [d.document_type for d in self.user_doctypes])})
|
||||
modules = frappe.get_all("DocType",
|
||||
fields=["module"],
|
||||
filters={"name": ("in", [d.document_type for d in self.user_doctypes])},
|
||||
distinct=True,
|
||||
)
|
||||
|
||||
self.set('user_type_modules', [])
|
||||
for row in modules:
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
.version-info {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.version-info pre {
|
||||
border: 0px;
|
||||
margin: 0px;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.version-info .table {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.version-info .success {
|
||||
background-color: #dff0d8 !important;
|
||||
}
|
||||
|
||||
.version-info .danger {
|
||||
background-color: #f2dede !important;
|
||||
}
|
||||
|
|
@ -256,11 +256,11 @@ class MariaDBDatabase(Database):
|
|||
index_name=index_name
|
||||
))
|
||||
|
||||
def add_index(self, doctype, fields, index_name=None):
|
||||
def add_index(self, doctype: str, fields: List, index_name: str = None):
|
||||
"""Creates an index with given fields if not already created.
|
||||
Index name will be `fieldname1_fieldname2_index`"""
|
||||
index_name = index_name or self.get_index_name(fields)
|
||||
table_name = 'tab' + doctype
|
||||
table_name = get_table_name(doctype)
|
||||
if not self.has_index(table_name, index_name):
|
||||
self.commit()
|
||||
self.sql("""ALTER TABLE `%s`
|
||||
|
|
|
|||
|
|
@ -258,14 +258,14 @@ class PostgresDatabase(Database):
|
|||
return self.sql("""SELECT 1 FROM pg_indexes WHERE tablename='{table_name}'
|
||||
and indexname='{index_name}' limit 1""".format(table_name=table_name, index_name=index_name))
|
||||
|
||||
def add_index(self, doctype, fields, index_name=None):
|
||||
def add_index(self, doctype: str, fields: List, index_name: str = None):
|
||||
"""Creates an index with given fields if not already created.
|
||||
Index name will be `fieldname1_fieldname2_index`"""
|
||||
table_name = get_table_name(doctype)
|
||||
index_name = index_name or self.get_index_name(fields)
|
||||
table_name = 'tab' + doctype
|
||||
fields_str = '", "'.join(re.sub(r"\(.*\)", "", field) for field in fields)
|
||||
|
||||
self.commit()
|
||||
self.sql("""CREATE INDEX IF NOT EXISTS "{}" ON `{}`("{}")""".format(index_name, table_name, '", "'.join(fields)))
|
||||
self.sql_ddl(f'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}` ("{fields_str}")')
|
||||
|
||||
def add_unique(self, doctype, fields, constraint_name=None):
|
||||
if isinstance(fields, str):
|
||||
|
|
|
|||
|
|
@ -20,5 +20,46 @@ frappe.ui.form.on('System Console', {
|
|||
$btn.text(__('Execute'));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
show_processlist: function(frm) {
|
||||
if (frm.doc.show_processlist) {
|
||||
// keep refreshing every 5 seconds
|
||||
frm.events.refresh_processlist(frm);
|
||||
frm.processlist_interval = setInterval(() => frm.events.refresh_processlist(frm), 5000);
|
||||
} else {
|
||||
if (frm.processlist_interval) {
|
||||
|
||||
// end it
|
||||
clearInterval(frm.processlist_interval);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
refresh_processlist: function(frm) {
|
||||
let timestamp = new Date();
|
||||
frappe.call('frappe.desk.doctype.system_console.system_console.show_processlist').then(r => {
|
||||
let rows = '';
|
||||
for (let row of r.message) {
|
||||
rows += `<tr>
|
||||
<td>${row.Id}</td>
|
||||
<td>${row.Time}</td>
|
||||
<td>${row.State}</td>
|
||||
<td>${row.Info}</td>
|
||||
<td>${row.Progress}</td>
|
||||
</tr>`
|
||||
}
|
||||
frm.get_field('processlist').html(`
|
||||
<p class='text-muted'>Requested on: ${timestamp}</p>
|
||||
<table class='table-bordered' style='width: 100%'>
|
||||
<thead><tr>
|
||||
<th width='10%'>Id</ht>
|
||||
<th width='10%'>Time</ht>
|
||||
<th width='10%'>State</ht>
|
||||
<th width='60%'>Info</ht>
|
||||
<th width='10%'>Progress</ht>
|
||||
</tr></thead>
|
||||
<tbody>${rows}</thead>`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,9 +17,13 @@
|
|||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"execute_section",
|
||||
"console",
|
||||
"commit",
|
||||
"output"
|
||||
"output",
|
||||
"database_processes_section",
|
||||
"show_processlist",
|
||||
"processlist"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -40,13 +44,34 @@
|
|||
"fieldname": "commit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Commit"
|
||||
},
|
||||
{
|
||||
"fieldname": "execute_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Execute"
|
||||
},
|
||||
{
|
||||
"fieldname": "database_processes_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Database Processes"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_processlist",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Processlist"
|
||||
},
|
||||
{
|
||||
"fieldname": "processlist",
|
||||
"fieldtype": "HTML",
|
||||
"label": "processlist"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-21 14:44:35.296877",
|
||||
"modified": "2021-09-09 13:10:14.237113",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "System Console",
|
||||
|
|
@ -65,4 +90,4 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -33,4 +33,9 @@ class SystemConsole(Document):
|
|||
def execute_code(doc):
|
||||
console = frappe.get_doc(json.loads(doc))
|
||||
console.run()
|
||||
return console.as_dict()
|
||||
return console.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
def show_processlist():
|
||||
frappe.only_for('System Manager')
|
||||
return frappe.db.sql('show full processlist', as_dict=1)
|
||||
|
|
|
|||
|
|
@ -164,7 +164,8 @@ doc_events = {
|
|||
"after_rename": "frappe.desk.notifications.clear_doctype_notifications",
|
||||
"on_cancel": [
|
||||
"frappe.desk.notifications.clear_doctype_notifications",
|
||||
"frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions"
|
||||
"frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions",
|
||||
"frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers"
|
||||
],
|
||||
"on_trash": [
|
||||
"frappe.desk.notifications.clear_doctype_notifications",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,17 @@
|
|||
frappe.ui.form.ControlFloat = class ControlFloat extends frappe.ui.form.ControlInt {
|
||||
|
||||
make_input() {
|
||||
super.make_input();
|
||||
const change_handler = e => {
|
||||
if (this.change) this.change(e);
|
||||
else {
|
||||
let value = this.get_input_value();
|
||||
this.parse_validate_and_set_in_model(value, e);
|
||||
}
|
||||
};
|
||||
// convert to number format on focusout since focus converts it to flt.
|
||||
this.$input.on("focusout", change_handler);
|
||||
}
|
||||
parse(value) {
|
||||
value = this.eval_expression(value);
|
||||
return isNaN(parseFloat(value)) ? null : flt(value, this.get_precision());
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ frappe.msgprint = function(msg, title, is_minimizable) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(data.alert) {
|
||||
if(data.alert || data.toast) {
|
||||
frappe.show_alert(data);
|
||||
return;
|
||||
}
|
||||
|
|
@ -361,7 +361,7 @@ frappe.hide_progress = function() {
|
|||
}
|
||||
|
||||
// Floating Message
|
||||
frappe.show_alert = function(message, seconds=7, actions={}) {
|
||||
frappe.show_alert = frappe.toast = function(message, seconds=7, actions={}) {
|
||||
let indicator_icon_map = {
|
||||
'orange': "solid-warning",
|
||||
'yellow': "solid-warning",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,12 @@ if (!window.frappe) window.frappe = {};
|
|||
function flt(v, decimals, number_format) {
|
||||
if (v == null || v == '') return 0;
|
||||
|
||||
if (typeof v !== "number") {
|
||||
if (!(typeof v === "number" || String(parseFloat(v)) == v)) {
|
||||
// cases in which this block should not run
|
||||
// 1. 'v' is already a number
|
||||
// 2. v is already parsed but in string form
|
||||
// if (typeof v !== "number") {
|
||||
|
||||
v = v + "";
|
||||
|
||||
// strip currency symbol if exists
|
||||
|
|
@ -25,6 +30,7 @@ function flt(v, decimals, number_format) {
|
|||
v = 0;
|
||||
}
|
||||
|
||||
v = parseFloat(v);
|
||||
if (decimals != null)
|
||||
return _round(v, decimals);
|
||||
return v;
|
||||
|
|
|
|||
|
|
@ -47,3 +47,4 @@
|
|||
@import "link_preview";
|
||||
@import "../common/quill";
|
||||
@import "plyr";
|
||||
@import "version";
|
||||
|
|
|
|||
33
frappe/public/scss/desk/version.scss
Normal file
33
frappe/public/scss/desk/version.scss
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
.version-info {
|
||||
overflow: auto;
|
||||
|
||||
pre {
|
||||
border: 0px;
|
||||
margin: 0px;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.table {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: var(--green-100) !important;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--red-100) !important;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
.version-info {
|
||||
.danger, .success {
|
||||
color: var(--gray-900);
|
||||
|
||||
td {
|
||||
color: var(--gray-900);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
@import "../common/flex";
|
||||
@import "../common/buttons";
|
||||
@import "../common/modal";
|
||||
@import "../desk/toast";
|
||||
@import "../common/indicator";
|
||||
@import "../common/controls";
|
||||
@import "../common/awesomeplete";
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ class TestDB(unittest.TestCase):
|
|||
frappe.delete_doc(test_doctype, doc)
|
||||
clear_custom_fields(test_doctype)
|
||||
|
||||
|
||||
@run_only_if(db_type_is.MARIADB)
|
||||
class TestDDLCommandsMaria(unittest.TestCase):
|
||||
test_table_name = "TestNotes"
|
||||
|
|
@ -205,7 +206,7 @@ class TestDDLCommandsMaria(unittest.TestCase):
|
|||
frappe.db.commit()
|
||||
frappe.db.sql(
|
||||
f"""
|
||||
CREATE TABLE `tab{self.test_table_name}` (`id` INT NULL,PRIMARY KEY (`id`));
|
||||
CREATE TABLE `tab{self.test_table_name}` (`id` INT NULL, content TEXT, PRIMARY KEY (`id`));
|
||||
"""
|
||||
)
|
||||
|
||||
|
|
@ -230,7 +231,10 @@ class TestDDLCommandsMaria(unittest.TestCase):
|
|||
|
||||
def test_describe(self) -> None:
|
||||
self.assertEqual(
|
||||
(("id", "int(11)", "NO", "PRI", None, ""),),
|
||||
(
|
||||
("id", "int(11)", "NO", "PRI", None, ""),
|
||||
("content", "text", "YES", "", None, ""),
|
||||
),
|
||||
frappe.db.describe(self.test_table_name),
|
||||
)
|
||||
|
||||
|
|
@ -240,6 +244,17 @@ class TestDDLCommandsMaria(unittest.TestCase):
|
|||
self.assertGreater(len(test_table_description), 0)
|
||||
self.assertIn("varchar(255)", test_table_description[0])
|
||||
|
||||
def test_add_index(self) -> None:
|
||||
index_name = "test_index"
|
||||
frappe.db.add_index(self.test_table_name, ["id", "content(50)"], index_name)
|
||||
indexs_in_table = frappe.db.sql(
|
||||
f"""
|
||||
SHOW INDEX FROM tab{self.test_table_name}
|
||||
WHERE Key_name = '{index_name}';
|
||||
"""
|
||||
)
|
||||
self.assertEquals(len(indexs_in_table), 2)
|
||||
|
||||
|
||||
@run_only_if(db_type_is.POSTGRES)
|
||||
class TestDDLCommandsPost(unittest.TestCase):
|
||||
|
|
@ -248,7 +263,7 @@ class TestDDLCommandsPost(unittest.TestCase):
|
|||
def setUp(self) -> None:
|
||||
frappe.db.sql(
|
||||
f"""
|
||||
CREATE TABLE "tab{self.test_table_name}" ("id" INT NULL,PRIMARY KEY ("id"))
|
||||
CREATE TABLE "tab{self.test_table_name}" ("id" INT NULL, content text, PRIMARY KEY ("id"))
|
||||
"""
|
||||
)
|
||||
|
||||
|
|
@ -273,7 +288,9 @@ class TestDDLCommandsPost(unittest.TestCase):
|
|||
self.test_table_name = new_table_name
|
||||
|
||||
def test_describe(self) -> None:
|
||||
self.assertEqual([("id",)], frappe.db.describe(self.test_table_name))
|
||||
self.assertEqual(
|
||||
[("id",), ("content",)], frappe.db.describe(self.test_table_name)
|
||||
)
|
||||
|
||||
def test_change_type(self) -> None:
|
||||
frappe.db.change_column_type(self.test_table_name, "id", "varchar(255)")
|
||||
|
|
@ -292,3 +309,15 @@ class TestDDLCommandsPost(unittest.TestCase):
|
|||
self.assertGreater(len(check_change), 0)
|
||||
self.assertIn("character varying", check_change[0])
|
||||
|
||||
def test_add_index(self) -> None:
|
||||
index_name = "test_index"
|
||||
frappe.db.add_index(self.test_table_name, ["id", "content(50)"], index_name)
|
||||
indexs_in_table = frappe.db.sql(
|
||||
f"""
|
||||
SELECT indexname
|
||||
FROM pg_indexes
|
||||
WHERE tablename = 'tab{self.test_table_name}'
|
||||
AND indexname = '{index_name}' ;
|
||||
""",
|
||||
)
|
||||
self.assertEquals(len(indexs_in_table), 1)
|
||||
|
|
@ -63,11 +63,12 @@ class TestTranslate(unittest.TestCase):
|
|||
Case 2: frappe.form_dict._lang is not set, but preferred_language cookie is
|
||||
"""
|
||||
|
||||
with patch.object(frappe.translate, "get_preferred_language_cookie", return_value=second_lang):
|
||||
set_request(method="POST", path="/", headers=[("Accept-Language", third_lang)])
|
||||
with patch.object(frappe.translate, "get_preferred_language_cookie", return_value='fr'):
|
||||
set_request(method="POST", path="/", headers=[("Accept-Language", 'hr')])
|
||||
return_val = get_language()
|
||||
|
||||
self.assertNotIn(return_val, [second_lang, get_parent_language(second_lang)])
|
||||
# system default language
|
||||
self.assertEqual(return_val, 'en')
|
||||
self.assertNotIn(return_val, [second_lang, get_parent_language(second_lang)])
|
||||
|
||||
def test_guest_request_language_resolution_with_cookie(self):
|
||||
"""Test for frappe.translate.get_language
|
||||
|
|
|
|||
|
|
@ -62,16 +62,15 @@ def create_todo_records():
|
|||
}).insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_communication_records():
|
||||
if frappe.db.get_all('Communication', {'subject': 'Test Form Communication 1'}):
|
||||
return
|
||||
|
||||
frappe.get_doc({
|
||||
def create_communication_record():
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"recipients": "test@gmail.com",
|
||||
"subject": "Test Form Communication 1",
|
||||
"communication_date": frappe.utils.now_datetime(),
|
||||
}).insert()
|
||||
})
|
||||
doc.insert()
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def setup_workflow():
|
||||
|
|
|
|||
|
|
@ -3262,7 +3262,7 @@ Drop,drop,
|
|||
Drop Here,Drop hier,
|
||||
Drop files here,Laat lêers hier neer,
|
||||
Dynamic Template,Dinamiese sjabloon,
|
||||
ERPNext Role,ERPVolgende rol,
|
||||
ERPNext Role,ERPNext rol,
|
||||
Email / Notifications,E-pos / kennisgewings,
|
||||
Email Account setup please enter your password for: {0},Voer u wagwoord in vir die e-posrekening vir: {0},
|
||||
Email Address whose Google Contacts are to be synced.,E-posadres waarvan die Google-kontakte gesinkroniseer moet word.,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,ጣል ያድርጉ።,
|
|||
Drop Here,እዚህ ጣል ያድርጉ።,
|
||||
Drop files here,ፋይሎችን እዚህ ይጣሉ።,
|
||||
Dynamic Template,ተለዋዋጭ አብነት,
|
||||
ERPNext Role,የኢአርኤክስ ቀጣይ ሚና።,
|
||||
ERPNext Role,ERPNext ሚና,
|
||||
Email / Notifications,ኢሜይል / ማስታወቂያዎች,
|
||||
Email Account setup please enter your password for: {0},የኢሜል አካውንት ማዋቀር እባክዎን ለሚከተለው ይለፍ ቃልዎን ያስገቡ ፦ {0},
|
||||
Email Address whose Google Contacts are to be synced.,የጉግል አድራሻዎች የሚመሳሰሉበት የኢሜል አድራሻ ፡፡,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,Dråbe,
|
|||
Drop Here,Drop Here,
|
||||
Drop files here,Slip filer her,
|
||||
Dynamic Template,Dynamisk skabelon,
|
||||
ERPNext Role,ERPNæste rolle,
|
||||
ERPNext Role,ERPNext rolle,
|
||||
Email / Notifications,E-mail / underretninger,
|
||||
Email Account setup please enter your password for: {0},Opsætning af e-mail-konto: indtast venligst din adgangskode til: {0},
|
||||
Email Address whose Google Contacts are to be synced.,"E-mail-adresse, hvis Google-kontakter skal synkroniseres.",
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,Penurunan,
|
|||
Drop Here,Jatuhkan Di Sini,
|
||||
Drop files here,Letakkan file di sini,
|
||||
Dynamic Template,Template Dinamis,
|
||||
ERPNext Role,Peran ERPN,
|
||||
ERPNext Role,Peran ERPNext,
|
||||
Email / Notifications,Notifikasi email,
|
||||
Email Account setup please enter your password for: {0},"Pengaturan Akun Email, harap masukkan kata sandi Anda untuk: {0}",
|
||||
Email Address whose Google Contacts are to be synced.,Alamat Email yang Kontak Google-nya harus disinkronkan.,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,Dropi,
|
|||
Drop Here,Sendu hér,
|
||||
Drop files here,Sendu skrár hér,
|
||||
Dynamic Template,Dynamískt sniðmát,
|
||||
ERPNext Role,ERPNæsta hlutverk,
|
||||
ERPNext Role,ERPNext hlutverk,
|
||||
Email / Notifications,Netfang / tilkynningar,
|
||||
Email Account setup please enter your password for: {0},Uppsetning tölvupóstreikninga vinsamlegast sláðu inn lykilorðið þitt fyrir: {0},
|
||||
Email Address whose Google Contacts are to be synced.,Netfang þar sem samstillt er Google tengiliði,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,Far cadere,
|
|||
Drop Here,Drop Here,
|
||||
Drop files here,Trascina i file qui,
|
||||
Dynamic Template,Modello dinamico,
|
||||
ERPNext Role,ERPSuccessivo ruolo,
|
||||
ERPNext Role,ruolo ERPNext,
|
||||
Email / Notifications,Notifiche di posta elettronica,
|
||||
Email Account setup please enter your password for: {0},"Impostazione dell'account e-mail, inserire la password per: {0}",
|
||||
Email Address whose Google Contacts are to be synced.,Indirizzo email i cui contatti Google devono essere sincronizzati.,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,ドロップ,
|
|||
Drop Here,ここにドロップ,
|
||||
Drop files here,ここにファイルをドロップします,
|
||||
Dynamic Template,動的テンプレート,
|
||||
ERPNext Role,ERP次のロール,
|
||||
ERPNext Role,ERPNext の役割,
|
||||
Email / Notifications,メール/通知,
|
||||
Email Account setup please enter your password for: {0},メールアカウントのセットアップ:{0}のパスワードを入力してください,
|
||||
Email Address whose Google Contacts are to be synced.,Googleの連絡先を同期するメールアドレス。,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,ទម្លាក់។,
|
|||
Drop Here,ទម្លាក់នៅទីនេះ។,
|
||||
Drop files here,ទម្លាក់ឯកសារនៅទីនេះ។,
|
||||
Dynamic Template,គំរូឌីណាមិក,
|
||||
ERPNext Role,តួនាទី ERP បន្ទាប់។,
|
||||
ERPNext Role,ERPNext តួនាទី។,
|
||||
Email / Notifications,អ៊ីមែល / ការជូនដំណឹង,
|
||||
Email Account setup please enter your password for: {0},រៀបចំគណនីអ៊ីមែលសូមបញ្ចូលពាក្យសម្ងាត់របស់អ្នកសម្រាប់៖ {0},
|
||||
Email Address whose Google Contacts are to be synced.,អាសយដ្ឋានអ៊ីមែលដែលទំនាក់ទំនងរបស់ Google នឹងត្រូវធ្វើសមកាលកម្ម។,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,थेंब,
|
|||
Drop Here,येथे ड्रॉप करा,
|
||||
Drop files here,फायली येथे सोडा,
|
||||
Dynamic Template,डायनॅमिक टेम्पलेट,
|
||||
ERPNext Role,ईआरपीनेक्स्ट रोल,
|
||||
ERPNext Role,ERPNext रोल,
|
||||
Email / Notifications,ईमेल / सूचना,
|
||||
Email Account setup please enter your password for: {0},ईमेल खाते सेटअप यासाठी आपला संकेतशब्द प्रविष्ट करा: {0},
|
||||
Email Address whose Google Contacts are to be synced.,ज्यांचे Google संपर्क समक्रमित केले जातील असा ईमेल पत्ता.,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,Tera,
|
|||
Drop Here,Tera Hano,
|
||||
Drop files here,Tera dosiye hano,
|
||||
Dynamic Template,Icyitegererezo,
|
||||
ERPNext Role,Uruhare rwa ERPN,
|
||||
ERPNext Role,uruhare rwa ERPNext,
|
||||
Email / Notifications,Imeri / Amatangazo,
|
||||
Email Account setup please enter your password for: {0},Imeri ya konte ya imeri nyamuneka andika ijambo ryibanga rya: {0},
|
||||
Email Address whose Google Contacts are to be synced.,Aderesi ya imeri abo Google igomba guhuza.,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,Pokles,
|
|||
Drop Here,Drop sem,
|
||||
Drop files here,Sem presuňte súbory,
|
||||
Dynamic Template,Dynamická šablóna,
|
||||
ERPNext Role,ERPĎalšia rola,
|
||||
ERPNext Role,ERPNext rola,
|
||||
Email / Notifications,E-mail / Upozornenia,
|
||||
Email Account setup please enter your password for: {0},"Nastavenie e-mailového účtu, zadajte heslo pre: {0}",
|
||||
Email Address whose Google Contacts are to be synced.,"E-mailová adresa, ktorej kontakty Google sa majú synchronizovať.",
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,Drop,
|
|||
Drop Here,Hidh këtu,
|
||||
Drop files here,Hidh skedarët këtu,
|
||||
Dynamic Template,Modeli Dinamik,
|
||||
ERPNext Role,Roli ERPN,
|
||||
ERPNext Role,Roli i ERPNext,
|
||||
Email / Notifications,Email / njoftime,
|
||||
Email Account setup please enter your password for: {0},Konfigurimi i llogarisë email ju lutemi shkruani fjalëkalimin tuaj për: {0,
|
||||
Email Address whose Google Contacts are to be synced.,Adresa e Email-it Kontaktet e të cilit Google duhet të sinkronizohen.,
|
||||
|
|
|
|||
|
|
|
@ -3262,7 +3262,7 @@ Drop,Släppa,
|
|||
Drop Here,Släpp här,
|
||||
Drop files here,Släpp filer här,
|
||||
Dynamic Template,Dynamisk mall,
|
||||
ERPNext Role,ERPNästa roll,
|
||||
ERPNext Role,ERPNext roll,
|
||||
Email / Notifications,E-post / aviseringar,
|
||||
Email Account setup please enter your password for: {0},"Ange e-postkonto, ange ditt lösenord för: {0}",
|
||||
Email Address whose Google Contacts are to be synced.,E-postadress vars Google-kontakter ska synkroniseras.,
|
||||
|
|
|
|||
|
|
|
@ -47,7 +47,7 @@ frappe.ui.form.on("Web Form", {
|
|||
|
||||
frm.add_custom_button(__('Get Fields'), () => {
|
||||
let webform_fieldtypes = frappe.meta.get_field('Web Form Field', 'fieldtype').options.split('\n');
|
||||
let fieldnames = (frm.doc.fields || []).map(d => d.fieldname);
|
||||
let fieldnames = (frm.doc.web_form_fields || []).map(d => d.fieldname);
|
||||
frappe.model.with_doctype(frm.doc.doc_type, () => {
|
||||
let meta = frappe.get_meta(frm.doc.doc_type);
|
||||
for (let field of meta.fields) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue