Bläddra i källkod

Merge branch 'develop' into ci_ubuntu_latest

version-14
Ankush Menat 3 år sedan
förälder
incheckning
049df53cd8
Ingen känd nyckel hittad för denna signaturen i databasen GPG-nyckel ID: 8EA82E09BBD13AAF
43 ändrade filer med 380 tillägg och 116 borttagningar
  1. +5
    -1
      codecov.yml
  2. +8
    -3
      cypress/integration/api.js
  3. +93
    -0
      cypress/integration/control_float.js
  4. +18
    -18
      cypress/integration/datetime_field_form_validation.js
  5. +2
    -2
      cypress/integration/list_view.js
  6. +7
    -8
      cypress/integration/sidebar.js
  7. +5
    -5
      cypress/integration/timeline.js
  8. +13
    -9
      cypress/support/commands.js
  9. +23
    -2
      frappe/__init__.py
  10. +1
    -1
      frappe/contacts/doctype/address/address.py
  11. +2
    -2
      frappe/contacts/doctype/contact/contact.py
  12. +1
    -0
      frappe/core/doctype/access_log/access_log.py
  13. +5
    -2
      frappe/core/doctype/user_type/user_type.py
  14. +0
    -21
      frappe/core/doctype/version/version.css
  15. +2
    -2
      frappe/database/mariadb/database.py
  16. +4
    -4
      frappe/database/postgres/database.py
  17. +41
    -0
      frappe/desk/doctype/system_console/system_console.js
  18. +28
    -3
      frappe/desk/doctype/system_console/system_console.json
  19. +6
    -1
      frappe/desk/doctype/system_console/system_console.py
  20. +2
    -1
      frappe/hooks.py
  21. +13
    -0
      frappe/public/js/frappe/form/controls/float.js
  22. +2
    -2
      frappe/public/js/frappe/ui/messages.js
  23. +7
    -1
      frappe/public/js/frappe/utils/number_format.js
  24. +1
    -0
      frappe/public/scss/desk/index.scss
  25. +33
    -0
      frappe/public/scss/desk/version.scss
  26. +1
    -0
      frappe/public/scss/website/index.scss
  27. +33
    -4
      frappe/tests/test_db.py
  28. +5
    -4
      frappe/tests/test_translate.py
  29. +5
    -6
      frappe/tests/ui_test_helpers.py
  30. +1
    -1
      frappe/translations/af.csv
  31. +1
    -1
      frappe/translations/am.csv
  32. +1
    -1
      frappe/translations/da.csv
  33. +1
    -1
      frappe/translations/id.csv
  34. +1
    -1
      frappe/translations/is.csv
  35. +1
    -1
      frappe/translations/it.csv
  36. +1
    -1
      frappe/translations/ja.csv
  37. +1
    -1
      frappe/translations/km.csv
  38. +1
    -1
      frappe/translations/mr.csv
  39. +1
    -1
      frappe/translations/rw.csv
  40. +1
    -1
      frappe/translations/sk.csv
  41. +1
    -1
      frappe/translations/sq.csv
  42. +1
    -1
      frappe/translations/sv.csv
  43. +1
    -1
      frappe/website/doctype/web_form/web_form.js

+ 5
- 1
codecov.yml Visa fil

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

+ 8
- 3
cypress/integration/api.js Visa fil

@@ -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
- 0
cypress/integration/control_float.js Visa fil

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

+ 18
- 18
cypress/integration/datetime_field_form_validation.js Visa fil

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

+ 2
- 2
cypress/integration/list_view.js Visa fil

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


+ 7
- 8
cypress/integration/sidebar.js Visa fil

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

+ 5
- 5
cypress/integration/timeline.js Visa fil

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


+ 13
- 9
cypress/support/commands.js Visa fil

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

+ 23
- 2
frappe/__init__.py Visa fil

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



+ 1
- 1
frappe/contacts/doctype/address/address.py Visa fil

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


+ 2
- 2
frappe/contacts/doctype/contact/contact.py Visa fil

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


+ 1
- 0
frappe/core/doctype/access_log/access_log.py Visa fil

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



+ 5
- 2
frappe/core/doctype/user_type/user_type.py Visa fil

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


+ 0
- 21
frappe/core/doctype/version/version.css Visa fil

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

+ 2
- 2
frappe/database/mariadb/database.py Visa fil

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


+ 4
- 4
frappe/database/postgres/database.py Visa fil

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


+ 41
- 0
frappe/desk/doctype/system_console/system_console.js Visa fil

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

+ 28
- 3
frappe/desk/doctype/system_console/system_console.json Visa fil

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

+ 6
- 1
frappe/desk/doctype/system_console/system_console.py Visa fil

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

+ 2
- 1
frappe/hooks.py Visa fil

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


+ 13
- 0
frappe/public/js/frappe/form/controls/float.js Visa fil

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


+ 2
- 2
frappe/public/js/frappe/ui/messages.js Visa fil

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


+ 7
- 1
frappe/public/js/frappe/utils/number_format.js Visa fil

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


+ 1
- 0
frappe/public/scss/desk/index.scss Visa fil

@@ -47,3 +47,4 @@
@import "link_preview";
@import "../common/quill";
@import "plyr";
@import "version";

+ 33
- 0
frappe/public/scss/desk/version.scss Visa fil

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

+ 1
- 0
frappe/public/scss/website/index.scss Visa fil

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


+ 33
- 4
frappe/tests/test_db.py Visa fil

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

+ 5
- 4
frappe/tests/test_translate.py Visa fil

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


+ 5
- 6
frappe/tests/ui_test_helpers.py Visa fil

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


+ 1
- 1
frappe/translations/af.csv Visa fil

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


+ 1
- 1
frappe/translations/am.csv Visa fil

@@ -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.,የጉግል አድራሻዎች የሚመሳሰሉበት የኢሜል አድራሻ ፡፡,


+ 1
- 1
frappe/translations/da.csv Visa fil

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


+ 1
- 1
frappe/translations/id.csv Visa fil

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


+ 1
- 1
frappe/translations/is.csv Visa fil

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


+ 1
- 1
frappe/translations/it.csv Visa fil

@@ -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&#39;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.,


+ 1
- 1
frappe/translations/ja.csv Visa fil

@@ -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の連絡先を同期するメールアドレス。,


+ 1
- 1
frappe/translations/km.csv Visa fil

@@ -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 នឹងត្រូវធ្វើសមកាលកម្ម។,


+ 1
- 1
frappe/translations/mr.csv Visa fil

@@ -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 संपर्क समक्रमित केले जातील असा ईमेल पत्ता.,


+ 1
- 1
frappe/translations/rw.csv Visa fil

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


+ 1
- 1
frappe/translations/sk.csv Visa fil

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


+ 1
- 1
frappe/translations/sq.csv Visa fil

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


+ 1
- 1
frappe/translations/sv.csv Visa fil

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


+ 1
- 1
frappe/website/doctype/web_form/web_form.js Visa fil

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


Laddar…
Avbryt
Spara