* fix: Add updated datepicker; fixed seconds formatting bug. Seconds between 0 and 9 were not zero-padded. * feat: Add framework for time format * feat: datetime server-side formatters. * tests: Added server-side datetime formatter tests * feat: Update client-side datetime formatters * tests: Add Cypress client-side formatting tests. * fix: JSON errors * fix: Update to not hard-code admin password * fix: Change to using bulk_update rather than the REST API * tests: Use Custom doctype for testing, not Standard * fix: Codacy style fixes * fix: Commonify update_datetime_picker in date.js, datetime.js, time.js Fix order of time_format in System Settings Restore get_user_fmt in utils/datetime.js * feat: Drastically reduce scale of Cypress testing (to make tests faster) Full testing is possible by setting 'fast_mode' to false in the spec file. * fix: Fix issues with datepicker/timepicker expansion * fix: typo * style: Various style fixes as requested by DeppSource: Python * fix: Timepicker not hiding on 'now' button. Force hiding on click. * style: Codacy style fixes. * fix: Use datepicker from node_modules * test: Refactor Datetime UI tests - cy.get_field - cy.set_value - cy.insert_doc with ignore_duplicate - Nominal datetime tests to cover most formats - Formatting with prettier * test: Datetime UI tests; wait for cur_frm.doc.datetime to update * tests: Add whitespace to typed input - Clear input only for Time field * test: Wait timeout 200 * test: Fix form test Co-authored-by: Faris Ansari <netchampfaris@users.noreply.github.com>version-14
@@ -0,0 +1,48 @@ | |||
export default { | |||
name: 'DateTime Test', | |||
custom: 1, | |||
actions: [], | |||
creation: '2019-03-15 06:29:07.215072', | |||
doctype: 'DocType', | |||
editable_grid: 1, | |||
engine: 'InnoDB', | |||
fields: [ | |||
{ | |||
fieldname: 'date', | |||
fieldtype: 'Date', | |||
label: 'Date' | |||
}, | |||
{ | |||
fieldname: 'time', | |||
fieldtype: 'Time', | |||
label: 'Time' | |||
}, | |||
{ | |||
fieldname: 'datetime', | |||
fieldtype: 'Datetime', | |||
label: 'Datetime' | |||
} | |||
], | |||
issingle: 1, | |||
links: [], | |||
modified: '2019-12-09 14:40:53.127615', | |||
modified_by: 'Administrator', | |||
module: 'Custom', | |||
owner: 'Administrator', | |||
permissions: [ | |||
{ | |||
create: 1, | |||
delete: 1, | |||
email: 1, | |||
print: 1, | |||
read: 1, | |||
role: 'System Manager', | |||
share: 1, | |||
write: 1 | |||
} | |||
], | |||
quick_entry: 1, | |||
sort_field: 'modified', | |||
sort_order: 'ASC', | |||
track_changes: 1 | |||
}; |
@@ -6,8 +6,8 @@ context('API Resources', () => { | |||
}); | |||
it('Creates two Comments', () => { | |||
cy.create_doc('Comment', {comment_type: 'Comment', content: "hello"}); | |||
cy.create_doc('Comment', {comment_type: 'Comment', content: "world"}); | |||
cy.insert_doc('Comment', {comment_type: 'Comment', content: "hello"}); | |||
cy.insert_doc('Comment', {comment_type: 'Comment', content: "world"}); | |||
}); | |||
it('Lists the Comments', () => { | |||
@@ -25,11 +25,11 @@ context('API Resources', () => { | |||
}); | |||
it('Gets each Comment', () => { | |||
cy.get_list('Comment').then(body => body.data.forEach(comment => { | |||
cy.get_list('Comment').then(body => body.data.forEach(comment => { | |||
cy.get_doc('Comment', comment.name); | |||
})); | |||
}); | |||
it('Removes the Comments', () => { | |||
cy.get_list('Comment').then(body => body.data.forEach(comment => { | |||
cy.remove_doc('Comment', comment.name); | |||
@@ -0,0 +1,128 @@ | |||
import datetime_doctype from '../fixtures/datetime_doctype'; | |||
const doctype_name = datetime_doctype.name; | |||
context('Control Date, Time and DateTime', () => { | |||
before(() => { | |||
cy.login(); | |||
cy.visit('/desk'); | |||
cy.insert_doc('DocType', datetime_doctype, true); | |||
}); | |||
describe('Date formats', () => { | |||
let date_formats = [ | |||
{ | |||
date_format: 'dd-mm-yyyy', | |||
part: 2, | |||
length: 4, | |||
separator: '-' | |||
}, | |||
{ | |||
date_format: 'mm/dd/yyyy', | |||
part: 0, | |||
length: 2, | |||
separator: '/' | |||
} | |||
]; | |||
date_formats.forEach(d => { | |||
it('test date format ' + d.date_format, () => { | |||
cy.set_value('System Settings', 'System Settings', { | |||
date_format: d.date_format | |||
}); | |||
cy.window() | |||
.its('frappe') | |||
.then(frappe => { | |||
// update sys_defaults value to avoid a reload | |||
frappe.sys_defaults.date_format = d.date_format; | |||
}); | |||
cy.new_form(doctype_name); | |||
cy.get('.form-control[data-fieldname=date]').focus(); | |||
cy.get('.datepickers-container .datepicker.active') | |||
.should('be.visible'); | |||
cy.get( | |||
'.datepickers-container .datepicker.active .datepicker--cell-day.-current-' | |||
).click(); | |||
cy.window() | |||
.its('cur_frm') | |||
.then(cur_frm => { | |||
let formatted_value = cur_frm.get_field('date').input.value; | |||
let parts = formatted_value.split(d.separator); | |||
expect(parts[d.part].length).to.equal(d.length); | |||
}); | |||
}); | |||
}); | |||
}); | |||
describe('Time formats', () => { | |||
let time_formats = [ | |||
{ | |||
time_format: 'HH:mm:ss', | |||
value: ' 11:00:12', | |||
match_value: '11:00:12' | |||
}, | |||
{ | |||
time_format: 'HH:mm', | |||
value: ' 11:00:12', | |||
match_value: '11:00' | |||
} | |||
]; | |||
time_formats.forEach(d => { | |||
it('test time format ' + d.time_format, () => { | |||
cy.set_value('System Settings', 'System Settings', { | |||
time_format: d.time_format | |||
}); | |||
cy.window() | |||
.its('frappe') | |||
.then(frappe => { | |||
frappe.sys_defaults.time_format = d.time_format; | |||
}); | |||
cy.new_form(doctype_name); | |||
cy.fill_field('time', d.value, 'Time').blur(); | |||
cy.get_field('time').should('have.value', d.match_value); | |||
}); | |||
}); | |||
}); | |||
describe('DateTime formats', () => { | |||
let datetime_formats = [ | |||
{ | |||
date_format: 'dd.mm.yyyy', | |||
time_format: 'HH:mm:ss', | |||
value: ' 02.12.2019 11:00:12', | |||
doc_value: '2019-12-02 11:00:12', | |||
input_value: '02.12.2019 11:00:12' | |||
}, | |||
{ | |||
date_format: 'mm-dd-yyyy', | |||
time_format: 'HH:mm', | |||
value: ' 12-02-2019 11:00:00', | |||
doc_value: '2019-12-02 11:00:00', | |||
input_value: '12-02-2019 11:00' | |||
} | |||
]; | |||
datetime_formats.forEach(d => { | |||
it(`test datetime format ${d.date_format} ${d.time_format}`, () => { | |||
cy.set_value('System Settings', 'System Settings', { | |||
date_format: d.date_format, | |||
time_format: d.time_format | |||
}); | |||
cy.window() | |||
.its('frappe') | |||
.then(frappe => { | |||
frappe.sys_defaults.date_format = d.date_format; | |||
frappe.sys_defaults.time_format = d.time_format; | |||
}); | |||
cy.new_form(doctype_name); | |||
cy.fill_field('datetime', d.value, 'Datetime').blur(); | |||
cy.get_field('datetime').should('have.value', d.input_value); | |||
cy.window() | |||
.its('cur_frm.doc.datetime') | |||
.should('eq', d.doc_value); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -16,10 +16,12 @@ context('Form', () => { | |||
cy.get('.primary-action').click(); | |||
cy.visit('/desk#List/ToDo'); | |||
cy.location('hash').should('eq', '#List/ToDo/List'); | |||
cy.get('h1').should('be.visible').and('contain', 'To Do'); | |||
cy.get('.list-row').should('contain', 'this is a test todo'); | |||
}); | |||
it('navigates between documents with child table list filters applied', () => { | |||
cy.visit('/desk#List/Contact'); | |||
cy.location('hash').should('eq', '#List/Contact/List'); | |||
cy.get('.tag-filters-area .btn:contains("Add Filter")').click(); | |||
cy.get('.fieldname-select-area').should('exist'); | |||
cy.get('.fieldname-select-area input').type('Number{enter}', { force: true }); | |||
@@ -42,95 +42,156 @@ Cypress.Commands.add('login', (email, password) => { | |||
}); | |||
Cypress.Commands.add('call', (method, args) => { | |||
return cy.window().its('frappe.csrf_token').then(csrf_token => { | |||
return cy.request({ | |||
url: `/api/method/${method}`, | |||
method: 'POST', | |||
body: args, | |||
headers: { | |||
'Accept': 'application/json', | |||
'Content-Type': 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
} | |||
}).then(res => { | |||
expect(res.status).eq(200); | |||
return res.body; | |||
return cy | |||
.window() | |||
.its('frappe.csrf_token') | |||
.then(csrf_token => { | |||
return cy | |||
.request({ | |||
url: `/api/method/${method}`, | |||
method: 'POST', | |||
body: args, | |||
headers: { | |||
Accept: 'application/json', | |||
'Content-Type': 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
} | |||
}) | |||
.then(res => { | |||
expect(res.status).eq(200); | |||
return res.body; | |||
}); | |||
}); | |||
}); | |||
}); | |||
Cypress.Commands.add('get_list', (doctype, fields=[], filters=[]) => { | |||
return cy.window().its('frappe.csrf_token').then(csrf_token => { | |||
return cy.request({ | |||
method: 'GET', | |||
url: `/api/resource/${doctype}?fields=${JSON.stringify(fields)}&filters=${JSON.stringify(filters)}`, | |||
headers: { | |||
'Accept': 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
} | |||
}).then(res => { | |||
expect(res.status).eq(200); | |||
return res.body; | |||
Cypress.Commands.add('get_list', (doctype, fields = [], filters = []) => { | |||
filters = JSON.stringify(filters); | |||
fields = JSON.stringify(fields); | |||
let url = `/api/resource/${doctype}?fields=${fields}&filters=${filters}`; | |||
return cy | |||
.window() | |||
.its('frappe.csrf_token') | |||
.then(csrf_token => { | |||
return cy | |||
.request({ | |||
method: 'GET', | |||
url, | |||
headers: { | |||
Accept: 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
} | |||
}) | |||
.then(res => { | |||
expect(res.status).eq(200); | |||
return res.body; | |||
}); | |||
}); | |||
}); | |||
}); | |||
Cypress.Commands.add('get_doc', (doctype, name) => { | |||
return cy.window().its('frappe.csrf_token').then(csrf_token => { | |||
return cy.request({ | |||
method: 'GET', | |||
url: `/api/resource/${doctype}/${name}`, | |||
headers: { | |||
'Accept': 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
} | |||
}).then(res => { | |||
expect(res.status).eq(200); | |||
return res.body; | |||
return cy | |||
.window() | |||
.its('frappe.csrf_token') | |||
.then(csrf_token => { | |||
return cy | |||
.request({ | |||
method: 'GET', | |||
url: `/api/resource/${doctype}/${name}`, | |||
headers: { | |||
Accept: 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
} | |||
}) | |||
.then(res => { | |||
expect(res.status).eq(200); | |||
return res.body; | |||
}); | |||
}); | |||
}); | |||
}); | |||
Cypress.Commands.add('create_doc', (doctype, args) => { | |||
return cy.window().its('frappe.csrf_token').then(csrf_token => { | |||
return cy.request({ | |||
method: 'POST', | |||
url: `/api/resource/${doctype}`, | |||
body: args, | |||
headers: { | |||
'Accept': 'application/json', | |||
'Content-Type': 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
} | |||
}).then(res => { | |||
expect(res.status).eq(200); | |||
return res.body; | |||
Cypress.Commands.add('insert_doc', (doctype, args, ignore_duplicate) => { | |||
return cy | |||
.window() | |||
.its('frappe.csrf_token') | |||
.then(csrf_token => { | |||
return cy | |||
.request({ | |||
method: 'POST', | |||
url: `/api/resource/${doctype}`, | |||
body: args, | |||
headers: { | |||
Accept: 'application/json', | |||
'Content-Type': 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
}, | |||
failOnStatusCode: !ignore_duplicate | |||
}) | |||
.then(res => { | |||
let status_codes = [200]; | |||
if (ignore_duplicate) { | |||
status_codes.push(409); | |||
} | |||
expect(res.status).to.be.oneOf(status_codes); | |||
return res.body; | |||
}); | |||
}); | |||
}); | |||
}); | |||
Cypress.Commands.add('remove_doc', (doctype, name) => { | |||
return cy.window().its('frappe.csrf_token').then(csrf_token => { | |||
return cy.request({ | |||
method: 'DELETE', | |||
url: `/api/resource/${doctype}/${name}`, | |||
headers: { | |||
'Accept': 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
} | |||
}).then(res => { | |||
expect(res.status).eq(202); | |||
return res.body; | |||
return cy | |||
.window() | |||
.its('frappe.csrf_token') | |||
.then(csrf_token => { | |||
return cy | |||
.request({ | |||
method: 'DELETE', | |||
url: `/api/resource/${doctype}/${name}`, | |||
headers: { | |||
Accept: 'application/json', | |||
'X-Frappe-CSRF-Token': csrf_token | |||
} | |||
}) | |||
.then(res => { | |||
expect(res.status).eq(202); | |||
return res.body; | |||
}); | |||
}); | |||
}); | |||
}); | |||
Cypress.Commands.add('create_records', (doc) => { | |||
return cy.call('frappe.tests.ui_test_helpers.create_if_not_exists', { doc }) | |||
Cypress.Commands.add('create_records', doc => { | |||
return cy | |||
.call('frappe.tests.ui_test_helpers.create_if_not_exists', { doc }) | |||
.then(r => r.message); | |||
}); | |||
Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => { | |||
Cypress.Commands.add('set_value', (doctype, name, obj) => { | |||
return cy.call('frappe.client.set_value', { | |||
doctype, | |||
name, | |||
fieldname: obj | |||
}); | |||
}); | |||
Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => { | |||
cy.get_field(fieldname, fieldtype).as('input'); | |||
if (['Date', 'Time', 'Datetime'].includes(fieldtype)) { | |||
cy.get('@input').click().wait(200); | |||
cy.get('.datepickers-container .datepicker.active').should('exist'); | |||
} | |||
if (fieldtype === 'Time') { | |||
cy.get('@input').clear(); | |||
} | |||
if (fieldtype === 'Select') { | |||
cy.get('@input').select(value); | |||
} else { | |||
cy.get('@input').type(value, { waitForAnimations: false }); | |||
} | |||
return cy.get('@input'); | |||
}); | |||
Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => { | |||
let selector = `.form-control[data-fieldname="${fieldname}"]`; | |||
if (fieldtype === 'Text Editor') { | |||
@@ -140,34 +201,33 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => { | |||
selector = `[data-fieldname="${fieldname}"] .ace_text-input`; | |||
} | |||
cy.get(selector).as('input'); | |||
if (fieldtype === 'Select') { | |||
return cy.get('@input').select(value); | |||
} else { | |||
return cy.get('@input').type(value, {waitForAnimations: false}); | |||
} | |||
return cy.get(selector); | |||
}); | |||
Cypress.Commands.add('awesomebar', (text) => { | |||
Cypress.Commands.add('awesomebar', text => { | |||
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, { delay: 100 }); | |||
}); | |||
Cypress.Commands.add('new_form', (doctype) => { | |||
cy.visit(`/desk#Form/${doctype}/New ${doctype} 1`); | |||
Cypress.Commands.add('new_form', doctype => { | |||
let route = `Form/${doctype}/New ${doctype} 1`; | |||
cy.visit(`/desk#${route}`); | |||
cy.get('body').should('have.attr', 'data-route', route); | |||
cy.get('body').should('have.attr', 'data-ajax-state', 'complete'); | |||
}); | |||
Cypress.Commands.add('go_to_list', (doctype) => { | |||
Cypress.Commands.add('go_to_list', doctype => { | |||
cy.visit(`/desk#List/${doctype}/List`); | |||
}); | |||
Cypress.Commands.add('clear_cache', () => { | |||
cy.window().its('frappe').then(frappe => { | |||
frappe.ui.toolbar.clear_cache(); | |||
}); | |||
cy.window() | |||
.its('frappe') | |||
.then(frappe => { | |||
frappe.ui.toolbar.clear_cache(); | |||
}); | |||
}); | |||
Cypress.Commands.add('dialog', (opts) => { | |||
Cypress.Commands.add('dialog', opts => { | |||
return cy.window().then(win => { | |||
var d = new win.frappe.ui.Dialog(opts); | |||
d.show(); | |||
@@ -180,7 +240,9 @@ Cypress.Commands.add('get_open_dialog', () => { | |||
}); | |||
Cypress.Commands.add('hide_dialog', () => { | |||
cy.get_open_dialog().find('.btn-modal-close').click(); | |||
cy.get_open_dialog() | |||
.find('.btn-modal-close') | |||
.click(); | |||
cy.get('.modal:visible').should('not.exist'); | |||
}); | |||
@@ -14,6 +14,7 @@ | |||
"setup_complete", | |||
"date_and_number_format", | |||
"date_format", | |||
"time_format", | |||
"column_break_7", | |||
"number_format", | |||
"float_precision", | |||
@@ -118,6 +119,14 @@ | |||
"options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\ndd.mm.yyyy\nmm/dd/yyyy\nmm-dd-yyyy", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"default": "HH:mm:ss", | |||
"fieldname": "time_format", | |||
"fieldtype": "Select", | |||
"label": "Time Format", | |||
"options": "HH:mm:ss\nHH:mm", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "column_break_7", | |||
"fieldtype": "Column Break" | |||
@@ -420,4 +429,4 @@ | |||
"sort_field": "modified", | |||
"sort_order": "ASC", | |||
"track_changes": 1 | |||
} | |||
} |
@@ -141,6 +141,7 @@ def update_system_settings(args): | |||
"time_zone": args.get("timezone"), | |||
"float_precision": 3, | |||
'date_format': frappe.db.get_value("Country", args.get("country"), "date_format"), | |||
'time_format': frappe.db.get_value("Country", args.get("country"), "time_format"), | |||
'number_format': number_format, | |||
'enable_scheduler': 1 if not frappe.flags.in_test else 0, | |||
'backup_limit': 3 # Default for downloadable backups | |||
@@ -10,8 +10,10 @@ from frappe.utils.momentjs import get_all_timezones | |||
def get_country_info(country=None): | |||
data = get_all() | |||
data = frappe._dict(data.get(country, {})) | |||
if not 'date_format' in data: | |||
if 'date_format' not in data: | |||
data.date_format = "dd-mm-yyyy" | |||
if 'time_format' not in data: | |||
data.time_format = "HH:mm:ss" | |||
return data | |||
@@ -76,6 +76,12 @@ | |||
"translatable": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"fieldname": "time_format", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Time format" | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
@@ -205,4 +211,4 @@ | |||
"track_changes": 1, | |||
"track_seen": 0, | |||
"track_views": 0 | |||
} | |||
} |
@@ -52,6 +52,22 @@ | |||
"website/js/bootstrap-4.js" | |||
], | |||
"js/control.min.js": [ | |||
"node_modules/air-datepicker/dist/js/datepicker.min.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.cs.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.da.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.de.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.en.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.es.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.fi.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.fr.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.hu.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.nl.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.pl.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.pt-BR.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.pt.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.ro.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.sk.js", | |||
"node_modules/air-datepicker/dist/js/i18n/datepicker.zh.js", | |||
"public/js/frappe/ui/capture.js", | |||
"public/js/frappe/form/controls/control.js" | |||
], | |||
@@ -114,8 +130,6 @@ | |||
"public/js/lib/socket.io.min.js", | |||
"public/js/lib/jSignature.min.js", | |||
"public/js/frappe/translate.js", | |||
"public/js/lib/datepicker/datepicker.min.js", | |||
"public/js/lib/datepicker/locale-all.js", | |||
"public/js/lib/leaflet/leaflet.js", | |||
"public/js/lib/leaflet/leaflet.draw.js", | |||
"public/js/lib/leaflet/L.Control.Locate.js", | |||
@@ -314,9 +328,7 @@ | |||
], | |||
"js/web_form.min.js": [ | |||
"public/js/frappe/utils/datetime.js", | |||
"public/js/frappe/web_form/webform_script.js", | |||
"public/js/lib/datepicker/datepicker.min.js", | |||
"public/js/lib/datepicker/datepicker.en.js" | |||
"public/js/frappe/web_form/webform_script.js" | |||
], | |||
"css/web_form.css": [ | |||
"public/less/list.less", | |||
@@ -1,14 +1,17 @@ | |||
frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ | |||
make_input: function() { | |||
this._super(); | |||
this.make_picker(); | |||
}, | |||
make_picker: function() { | |||
this.set_date_options(); | |||
this.set_datepicker(); | |||
this.set_t_for_today(); | |||
}, | |||
set_formatted_input: function(value) { | |||
this._super(value); | |||
if (this.timepicker_only) return; | |||
if (!this.datepicker) return; | |||
if(!value) { | |||
this.datepicker.clear(); | |||
@@ -71,19 +74,6 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ | |||
} | |||
}; | |||
}, | |||
update_datepicker_position: function() { | |||
if(!this.frm) return; | |||
// show datepicker above or below the input | |||
// based on scroll position | |||
var window_height = $(window).height(); | |||
var window_scroll_top = $(window).scrollTop(); | |||
var el_offset_top = this.$input.offset().top + 280; | |||
var position = 'top left'; | |||
if(window_height + window_scroll_top >= el_offset_top) { | |||
position = 'bottom left'; | |||
} | |||
this.datepicker.update('position', position); | |||
}, | |||
set_datepicker: function() { | |||
this.$input.datepicker(this.datepicker_options); | |||
this.datepicker = this.$input.data('datepicker'); | |||
@@ -96,6 +86,29 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ | |||
this.datepicker.selectDate(this.get_now_date()); | |||
}); | |||
}, | |||
update_datepicker_position: function() { | |||
if(!this.frm) return; | |||
// show datepicker above or below the input | |||
// based on scroll position | |||
// We have to bodge around the timepicker getting its position | |||
// wrong by 42px when opening upwards. | |||
const $header = $('.page-head'); | |||
const header_bottom = $header.position().top + $header.outerHeight(); | |||
const picker_height = this.datepicker.$datepicker.outerHeight() + 12; | |||
const picker_top = this.$input.offset().top - $(window).scrollTop() - picker_height; | |||
var position = 'top left'; | |||
// 12 is the default datepicker.opts[offset] | |||
if (picker_top <= header_bottom) { | |||
position = 'bottom left'; | |||
if (this.timepicker_only) this.datepicker.opts['offset'] = 12; | |||
} else { | |||
// To account for 42px incorrect positioning | |||
if (this.timepicker_only) this.datepicker.opts['offset'] = -30; | |||
} | |||
this.datepicker.update('position', position); | |||
}, | |||
get_now_date: function() { | |||
return frappe.datetime.now_date(true); | |||
}, | |||
@@ -2,10 +2,13 @@ frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ | |||
set_date_options: function() { | |||
this._super(); | |||
this.today_text = __("Now"); | |||
let sysdefaults = frappe.boot.sysdefaults; | |||
this.date_format = frappe.defaultDatetimeFormat; | |||
let time_format = sysdefaults && sysdefaults.time_format | |||
? sysdefaults.time_format : 'HH:mm:ss'; | |||
$.extend(this.datepicker_options, { | |||
timepicker: true, | |||
timeFormat: "hh:ii:ss" | |||
timeFormat: time_format.toLowerCase().replace("mm", "ii") | |||
}); | |||
}, | |||
get_now_date: function() { | |||
@@ -22,5 +25,15 @@ frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ | |||
} | |||
} | |||
this._super(); | |||
}, | |||
set_datepicker: function() { | |||
this._super(); | |||
if (this.datepicker.opts.timeFormat.indexOf('s') == -1) { | |||
// No seconds in time format | |||
const $tp = this.datepicker.timepicker; | |||
$tp.$seconds.parent().css('display', 'none'); | |||
$tp.$secondsText.css('display', 'none'); | |||
$tp.$secondsText.prev().css('display', 'none'); | |||
} | |||
} | |||
}); |
@@ -1,43 +1,72 @@ | |||
frappe.ui.form.ControlTime = frappe.ui.form.ControlData.extend({ | |||
frappe.ui.form.ControlTime = frappe.ui.form.ControlDate.extend({ | |||
set_formatted_input: function(value) { | |||
this._super(value); | |||
}, | |||
make_input: function() { | |||
var me = this; | |||
this.timepicker_only = true; | |||
this._super(); | |||
this.$input.datepicker({ | |||
}, | |||
make_picker: function() { | |||
this.set_time_options(); | |||
this.set_datepicker(); | |||
this.refresh(); | |||
}, | |||
set_time_options: function() { | |||
let sysdefaults = frappe.boot.sysdefaults; | |||
let time_format = sysdefaults && sysdefaults.time_format | |||
? sysdefaults.time_format : 'HH:mm:ss'; | |||
this.time_format = frappe.defaultTimeFormat; | |||
this.datepicker_options = { | |||
language: "en", | |||
timepicker: true, | |||
onlyTimepicker: true, | |||
timeFormat: "hh:ii:ss", | |||
timeFormat: time_format.toLowerCase().replace("mm", "ii"), | |||
startDate: frappe.datetime.now_time(true), | |||
onSelect: function() { | |||
onSelect: () => { | |||
// ignore micro seconds | |||
if (moment(me.get_value(), 'hh:mm:ss').format('HH:mm:ss') != moment(me.value, 'hh:mm:ss').format('HH:mm:ss')) { | |||
me.$input.trigger('change'); | |||
} | |||
if (moment(this.get_value(), time_format).format('HH:mm:ss') != moment(this.value, time_format).format('HH:mm:ss')) { | |||
this.$input.trigger('change'); | |||
} | |||
}, | |||
onShow: function() { | |||
onShow: () => { | |||
$('.datepicker--button:visible').text(__('Now')); | |||
this.update_datepicker_position(); | |||
}, | |||
keyboardNav: false, | |||
todayButton: true | |||
}); | |||
this.datepicker = this.$input.data('datepicker'); | |||
this.datepicker.$datepicker | |||
.find('[data-action="today"]') | |||
.click(() => { | |||
this.datepicker.selectDate(frappe.datetime.now_time(true)); | |||
}); | |||
this.refresh(); | |||
}; | |||
}, | |||
set_input: function(value) { | |||
this._super(value); | |||
if(value | |||
if (value | |||
&& ((this.last_value && this.last_value !== this.value) | |||
|| (!this.datepicker.selectedDates.length))) { | |||
var date_obj = frappe.datetime.moment_to_date_obj(moment(value, 'HH:mm:ss')); | |||
var date_obj = frappe.datetime.moment_to_date_obj(moment(value, frappe.sys_defaults['time_format'])); | |||
this.datepicker.selectDate(date_obj); | |||
} | |||
}, | |||
set_datepicker: function() { | |||
this.$input.datepicker(this.datepicker_options); | |||
this.datepicker = this.$input.data('datepicker'); | |||
this.datepicker.$datepicker | |||
.find('[data-action="today"]') | |||
.click(() => { | |||
this.datepicker.selectDate(frappe.datetime.now_time(true)); | |||
this.datepicker.hide(); | |||
}); | |||
if (this.datepicker.opts.timeFormat.indexOf('s') == -1) { | |||
// No seconds in time format | |||
const $tp = this.datepicker.timepicker; | |||
$tp.$seconds.parent().css('display', 'none'); | |||
$tp.$secondsText.css('display', 'none'); | |||
$tp.$secondsText.prev().css('display', 'none'); | |||
} | |||
}, | |||
set_description: function() { | |||
const { description } = this.df; | |||
const { time_zone } = frappe.sys_defaults; | |||
@@ -49,5 +78,26 @@ frappe.ui.form.ControlTime = frappe.ui.form.ControlData.extend({ | |||
} | |||
} | |||
this._super(); | |||
}, | |||
parse: function(value) { | |||
if (value) { | |||
return frappe.datetime.user_to_str(value, true); | |||
} | |||
}, | |||
format_for_input: function(value) { | |||
if (value) { | |||
return frappe.datetime.str_to_user(value, true); | |||
} | |||
return ""; | |||
}, | |||
validate: function(value) { | |||
if (value && !frappe.datetime.validate(value)) { | |||
let sysdefaults = frappe.sys_defaults; | |||
let time_format = sysdefaults && sysdefaults.time_format | |||
? sysdefaults.time_format : 'HH:mm:ss'; | |||
frappe.msgprint(__("Time {0} must be in format: {1}", [value, time_format])); | |||
return ''; | |||
} | |||
return value; | |||
} | |||
}); |
@@ -156,7 +156,8 @@ frappe.form.formatters = { | |||
if(frappe.boot.sysdefaults.time_zone) { | |||
m = m.tz(frappe.boot.sysdefaults.time_zone); | |||
} | |||
return m.format(frappe.boot.sysdefaults.date_format.toUpperCase() + ', h:mm a z'); | |||
return m.format(frappe.boot.sysdefaults.date_format.toUpperCase() | |||
+ ' ' + frappe.boot.sysdefaults.time_format); | |||
} else { | |||
return ""; | |||
} | |||
@@ -180,6 +181,13 @@ frappe.form.formatters = { | |||
return frappe.form.formatters.Data(value); | |||
}, | |||
Time: function(value) { | |||
if (value) { | |||
value = frappe.datetime.str_to_user(value, true); | |||
} | |||
return value || ""; | |||
}, | |||
LikedBy: function(value) { | |||
var html = ""; | |||
$.each(JSON.parse(value || "[]"), function(i, v) { | |||
@@ -51,7 +51,7 @@ $.extend(frappe.datetime, { | |||
}, | |||
obj_to_user: function(d) { | |||
return moment(d).format(frappe.datetime.get_user_fmt().toUpperCase()); | |||
return moment(d).format(frappe.datetime.get_user_date_fmt().toUpperCase()); | |||
}, | |||
get_diff: function(d1, d2) { | |||
@@ -106,23 +106,32 @@ $.extend(frappe.datetime, { | |||
return moment().endOf("year").format(); | |||
}, | |||
get_user_fmt: function() { | |||
get_user_time_fmt: function() { | |||
return frappe.sys_defaults && frappe.sys_defaults.time_format || "HH:mm:ss"; | |||
}, | |||
get_user_date_fmt: function() { | |||
return frappe.sys_defaults && frappe.sys_defaults.date_format || "yyyy-mm-dd"; | |||
}, | |||
get_user_fmt: function() { // For backwards compatibility only | |||
return frappe.sys_defaults && frappe.sys_defaults.date_format || "yyyy-mm-dd"; | |||
}, | |||
str_to_user: function(val, only_time = false) { | |||
if(!val) return ""; | |||
var user_time_fmt = frappe.datetime.get_user_time_fmt(); | |||
if(only_time) { | |||
return moment(val, frappe.defaultTimeFormat) | |||
.format(frappe.defaultTimeFormat); | |||
.format(user_time_fmt); | |||
} | |||
var user_fmt = frappe.datetime.get_user_fmt().toUpperCase(); | |||
var user_date_fmt = frappe.datetime.get_user_date_fmt().toUpperCase(); | |||
if(typeof val !== "string" || val.indexOf(" ")===-1) { | |||
return moment(val).format(user_fmt); | |||
return moment(val).format(user_date_fmt); | |||
} else { | |||
return moment(val, "YYYY-MM-DD HH:mm:ss").format(user_fmt + " HH:mm:ss"); | |||
return moment(val, "YYYY-MM-DD HH:mm:ss").format(user_date_fmt + " " + user_time_fmt); | |||
} | |||
}, | |||
@@ -132,16 +141,17 @@ $.extend(frappe.datetime, { | |||
user_to_str: function(val, only_time = false) { | |||
var user_time_fmt = frappe.datetime.get_user_time_fmt(); | |||
if(only_time) { | |||
return moment(val, frappe.defaultTimeFormat) | |||
return moment(val, user_time_fmt) | |||
.format(frappe.defaultTimeFormat); | |||
} | |||
var user_fmt = frappe.datetime.get_user_fmt().toUpperCase(); | |||
var user_fmt = frappe.datetime.get_user_date_fmt().toUpperCase(); | |||
var system_fmt = "YYYY-MM-DD"; | |||
if(val.indexOf(" ")!==-1) { | |||
user_fmt += " HH:mm:ss"; | |||
user_fmt += " " + user_time_fmt; | |||
system_fmt += " HH:mm:ss"; | |||
} | |||
@@ -1,12 +0,0 @@ | |||
;(function ($) { $.fn.datepicker.language['en'] = { | |||
days: [__('Sunday'), __('Monday'), __('Tuesday'), __('Wednesday'), __('Thursday'), __('Friday'), __('Saturday')], | |||
daysShort: [__('Sun'), __('Mon'), __('Tue'), __('Wed'), __('Thu'), __('Fri'), __('Sat')], | |||
daysMin: [__('Su'), __('Mo'), __('Tu'), __('We'), __('Th'), __('Fr'), __('Sa')], | |||
months: [__('January'),__('February'),__('March'),__('April'),__('May'),__('June'), __('July'),__('August'),__('September'),__('October'),__('November'),__('December')], | |||
monthsShort: [__('Jan'), __('Feb'), __('Mar'), __('Apr'), __('May'), __('Jun'), __('Jul'), __('Aug'), __('Sep'), __('Oct'), __('Nov'), __('Dec')], | |||
today: __('Today'), | |||
clear: __('Clear'), | |||
dateFormat: 'mm/dd/yyyy', | |||
timeFormat: 'hh:ii aa', | |||
firstDay: 0 | |||
}; })(jQuery); |
@@ -1,263 +0,0 @@ | |||
;(function ($) { $.fn.datepicker.language['ar'] = { | |||
days: ['الأحد', 'الأثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعه', 'السبت'], | |||
daysShort: ['الأحد', 'الأثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعه', 'السبت'], | |||
daysMin: ['الأحد', 'الأثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعه', 'السبت'], | |||
months: ['يناير','فبراير','مارس','أبريل','مايو','يونيو', 'يوليو','أغسطس','سبتمبر','اكتوبر','نوفمبر','ديسمبر'], | |||
monthsShort: ['يناير','فبراير','مارس','أبريل','مايو','يونيو', 'يوليو','أغسطس','سبتمبر','اكتوبر','نوفمبر','ديسمبر'], | |||
today: 'اليوم', | |||
clear: 'Clear', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii aa', | |||
firstDay: 0 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['cs'] = { | |||
days: ['Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'], | |||
daysShort: ['Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So'], | |||
daysMin: ['Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So'], | |||
months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'], | |||
monthsShort: ['Led', 'Úno', 'Bře', 'Dub', 'Kvě', 'Čvn', 'Čvc', 'Srp', 'Zář', 'Říj', 'Lis', 'Pro'], | |||
today: 'Dnes', | |||
clear: 'Vymazat', | |||
dateFormat: 'dd.mm.yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['da'] = { | |||
days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'], | |||
daysShort: ['Søn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør'], | |||
daysMin: ['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø'], | |||
months: ['Januar','Februar','Marts','April','Maj','Juni', 'Juli','August','September','Oktober','November','December'], | |||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'], | |||
today: 'I dag', | |||
clear: 'Nulstil', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; })(jQuery) | |||
;(function ($) { $.fn.datepicker.language['de'] = { | |||
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], | |||
daysShort: ['Son', 'Mon', 'Die', 'Mit', 'Don', 'Fre', 'Sam'], | |||
daysMin: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], | |||
months: ['Januar','Februar','März','April','Mai','Juni', 'Juli','August','September','Oktober','November','Dezember'], | |||
monthsShort: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], | |||
today: 'Heute', | |||
clear: 'Aufräumen', | |||
dateFormat: 'dd.mm.yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['en'] = { | |||
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], | |||
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], | |||
daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], | |||
months: ['January','February','March','April','May','June', 'July','August','September','October','November','December'], | |||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], | |||
today: 'Today', | |||
clear: 'Clear', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii aa', | |||
firstDay: 0 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['en-GB'] = { | |||
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], | |||
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], | |||
daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], | |||
months: ['January','February','March','April','May','June', 'July','August','September','October','November','December'], | |||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], | |||
today: 'Today', | |||
clear: 'Clear', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii aa', | |||
firstDay: 1 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['en-US'] = { | |||
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], | |||
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], | |||
daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], | |||
months: ['January','February','March','April','May','June', 'July','August','September','October','November','December'], | |||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], | |||
today: 'Today', | |||
clear: 'Clear', | |||
dateFormat: 'mm/dd/yyyy', | |||
timeFormat: 'hh:ii aa', | |||
firstDay: 0 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['es'] = { | |||
days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'], | |||
daysShort: ['Dom', 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab'], | |||
daysMin: ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa'], | |||
months: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Augosto','Septiembre','Octubre','Noviembre','Diciembre'], | |||
monthsShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'], | |||
today: 'Hoy', | |||
clear: 'Limpiar', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii aa', | |||
firstDay: 1 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['fi'] = { | |||
days: ['Sunnuntai', 'Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai'], | |||
daysShort: ['Su', 'Ma', 'Ti', 'Ke', 'To', 'Pe', 'La'], | |||
daysMin: ['Su', 'Ma', 'Ti', 'Ke', 'To', 'Pe', 'La'], | |||
months: ['Tammikuu','Helmikuu','Maaliskuu','Huhtikuu','Toukokuu','Kesäkuu', 'Heinäkuu','Elokuu','Syyskuu','Lokakuu','Marraskuu','Joulukuu'], | |||
monthsShort: ['Tammi', 'Helmi', 'Maalis', 'Huhti', 'Touko', 'Kesä', 'Heinä', 'Elo', 'Syys', 'Loka', 'Marras', 'Joulu'], | |||
today: 'Tänään', | |||
clear: 'Tyhjennä', | |||
dateFormat: 'dd.mm.yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; | |||
})(jQuery); | |||
;(function ($) { $.fn.datepicker.language['fr'] = { | |||
days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'], | |||
daysShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'], | |||
daysMin: ['Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa'], | |||
months: ['Janvier','Février','Mars','Avril','Mai','Juin', 'Juillet','Août','Septembre','Octobre','Novembre','Decembre'], | |||
monthsShort: ['Jan', 'Fév', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Dec'], | |||
today: "Aujourd'hui", | |||
clear: 'Effacer', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['gr'] = { | |||
days: ['Κυριακή', 'Δευτέρα', 'Τρίτη', 'Τετάρτη', 'Πέμπτη', 'Παρασκευή', 'Σάββατο'], | |||
daysShort: ['Κυρ', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ'], | |||
daysMin: ['Κυ', 'Δε', 'Τρ', 'Τε', 'Πε', 'Πα', 'Σα'], | |||
months: ['Ιανουάριος', 'Φεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'Ιούνιος', 'Ιούλιος', 'Αύγουστος', 'Σεπτέμβριος', 'Οκτώβριος', 'Νοέμβριος', 'Δεκέμβριος'], | |||
monthsShort: ['Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μάι', 'Ι/ν', 'Ι/λ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'], | |||
today: 'Σήμερα', | |||
clear: 'Καθαρισμός', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii aa', | |||
firstDay: 0 | |||
}; })(jQuery); | |||
;(function ($) { ;(function ($) { $.fn.datepicker.language['hu'] = { | |||
days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'Csütörtök', 'Péntek', 'Szombat'], | |||
daysShort: ['Va', 'Hé', 'Ke', 'Sze', 'Cs', 'Pé', 'Szo'], | |||
daysMin: ['V', 'H', 'K', 'Sz', 'Cs', 'P', 'Sz'], | |||
months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'], | |||
monthsShort: ['Jan', 'Feb', 'Már', 'Ápr', 'Máj', 'Jún', 'Júl', 'Aug', 'Szep', 'Okt', 'Nov', 'Dec'], | |||
today: 'Ma', | |||
clear: 'Törlés', | |||
dateFormat: 'yyyy-mm-dd', | |||
timeFormat: 'hh:ii aa', | |||
firstDay: 1 | |||
}; })(jQuery); })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['it'] = { | |||
days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'], | |||
daysShort: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'], | |||
daysMin: ['Do', 'Lu', 'Ma', 'Me', 'Gi', 'Ve', 'Sa'], | |||
months: ['Gennaio','Febbraio','Marzo','Aprile','Maggio','Giugno','Luglio','Agosto', | |||
'Settembre','Ottobre','Novembre','Dicembre'], | |||
monthsShort: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'], | |||
today: 'Oggi', | |||
clear: 'Reset', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['nl'] = { | |||
days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], | |||
daysShort: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], | |||
daysMin: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], | |||
months: ['Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'], | |||
monthsShort: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'], | |||
today: 'Vandaag', | |||
clear: 'Legen', | |||
dateFormat: 'dd-MM-yy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 0 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['pl'] = { | |||
days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'], | |||
daysShort: ['Nie', 'Pon', 'Wto', 'Śro', 'Czw', 'Pią', 'Sob'], | |||
daysMin: ['Nd', 'Pn', 'Wt', 'Śr', 'Czw', 'Pt', 'So'], | |||
months: ['Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec', 'Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'], | |||
monthsShort: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'], | |||
today: 'Dzisiaj', | |||
clear: 'Wyczyść', | |||
dateFormat: 'yyyy-mm-dd', | |||
timeFormat: 'hh:ii:aa', | |||
firstDay: 1 | |||
}; | |||
})(jQuery); | |||
;(function ($) { $.fn.datepicker.language['pt-BR'] = { | |||
days: ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'], | |||
daysShort: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab'], | |||
daysMin: ['Do', 'Se', 'Te', 'Qu', 'Qu', 'Se', 'Sa'], | |||
months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'], | |||
monthsShort: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], | |||
today: 'Hoje', | |||
clear: 'Limpar', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 0 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['pt'] = { | |||
days: ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'], | |||
daysShort: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab'], | |||
daysMin: ['Do', 'Se', 'Te', 'Qa', 'Qi', 'Sx', 'Sa'], | |||
months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'], | |||
monthsShort: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], | |||
today: 'Hoje', | |||
clear: 'Limpar', | |||
dateFormat: 'dd/mm/yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['ro'] = { | |||
days: ['Duminică', 'Luni', 'Marţi', 'Miercuri', 'Joi', 'Vineri', 'Sâmbătă'], | |||
daysShort: ['Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm'], | |||
daysMin: ['D', 'L', 'Ma', 'Mi', 'J', 'V', 'S'], | |||
months: ['Ianuarie','Februarie','Martie','Aprilie','Mai','Iunie','Iulie','August','Septembrie','Octombrie','Noiembrie','Decembrie'], | |||
monthsShort: ['Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'], | |||
today: 'Azi', | |||
clear: 'Şterge', | |||
dateFormat: 'dd.mm.yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['sk'] = { | |||
days: ['Nedeľa', 'Pondelok', 'Utorok', 'Streda', 'Štvrtok', 'Piatok', 'Sobota'], | |||
daysShort: ['Ned', 'Pon', 'Uto', 'Str', 'Štv', 'Pia', 'Sob'], | |||
daysMin: ['Ne', 'Po', 'Ut', 'St', 'Št', 'Pi', 'So'], | |||
months: ['Január','Február','Marec','Apríl','Máj','Jún', 'Júl','August','September','Október','November','December'], | |||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Máj', 'Jún', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'], | |||
today: 'Dnes', | |||
clear: 'Vymazať', | |||
dateFormat: 'dd.mm.yyyy', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; })(jQuery); | |||
;(function ($) { $.fn.datepicker.language['zh'] = { | |||
days: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'], | |||
daysShort: ['日', '一', '二', '三', '四', '五', '六'], | |||
daysMin: ['日', '一', '二', '三', '四', '五', '六'], | |||
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], | |||
monthsShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], | |||
today: '今天', | |||
clear: '清除', | |||
dateFormat: 'yyyy-mm-dd', | |||
timeFormat: 'hh:ii', | |||
firstDay: 1 | |||
}; })(jQuery); |
@@ -1,5 +1,5 @@ | |||
@import "variables.less"; | |||
@import (less) "../js/lib/datepicker/datepicker.min.css"; | |||
@import (less) "../../../node_modules/air-datepicker/dist/css/datepicker.min.css"; | |||
.datepicker { | |||
font-family: inherit; | |||
@@ -0,0 +1,163 @@ | |||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import datetime | |||
import frappe | |||
from frappe.utils import ( | |||
getdate, get_datetime, get_time, | |||
get_user_date_format, get_user_time_format, | |||
formatdate, format_datetime, format_time) | |||
import unittest | |||
test_date_obj = datetime.datetime.now() | |||
test_date = test_date_obj.strftime('%Y-%m-%d') | |||
test_time = test_date_obj.strftime('%H:%M:%S.%f') | |||
test_datetime = test_date_obj.strftime('%Y-%m-%d %H:%M:%S.%f') | |||
test_date_formats = { | |||
'yyyy-mm-dd': test_date_obj.strftime('%Y-%m-%d'), | |||
'dd-mm-yyyy': test_date_obj.strftime('%d-%m-%Y'), | |||
'dd/mm/yyyy': test_date_obj.strftime('%d/%m/%Y'), | |||
'dd.mm.yyyy': test_date_obj.strftime('%d.%m.%Y'), | |||
'mm/dd/yyyy': test_date_obj.strftime('%m/%d/%Y'), | |||
'mm-dd-yyyy': test_date_obj.strftime('%m-%d-%Y')} | |||
test_time_formats = { | |||
'HH:mm:ss': test_date_obj.strftime('%H:%M:%S'), | |||
'HH:mm': test_date_obj.strftime('%H:%M')} | |||
class TestFmtDatetime(unittest.TestCase): | |||
"""Tests date, time and datetime formatters and some associated | |||
utility functions. These rely on the system-wide date and time | |||
formats. | |||
""" | |||
# Set up and tidy up routines | |||
def setUp(self): | |||
# create test domain | |||
self.pre_test_date_format = frappe.db.get_default("date_format") | |||
self.pre_test_time_format = frappe.db.get_default("time_format") | |||
def tearDown(self): | |||
frappe.db.set_default("date_format", self.pre_test_date_format) | |||
frappe.db.set_default("time_format", self.pre_test_time_format) | |||
frappe.local.user_date_format = None | |||
frappe.local.user_time_format = None | |||
# Test utility functions | |||
def test_set_default_date_format(self): | |||
frappe.db.set_default("date_format", "ZYX321") | |||
self.assertEqual(frappe.db.get_default("date_format"), "ZYX321") | |||
def test_set_default_time_format(self): | |||
frappe.db.set_default("time_format", "XYZ123") | |||
self.assertEqual(frappe.db.get_default("time_format"), "XYZ123") | |||
def test_get_functions(self): | |||
# Test round-trip through getdate, get_datetime and get_time | |||
self.assertEqual(test_date_obj, get_datetime(test_datetime)) | |||
self.assertEqual(test_date_obj.date(), getdate(test_date)) | |||
self.assertEqual(test_date_obj.time(), get_time(test_time)) | |||
# Test date formatters | |||
def test_formatdate_forced(self): | |||
# Test with forced date formats | |||
self.assertEqual( | |||
formatdate(test_date, 'dd-yyyy-mm'), | |||
test_date_obj.strftime('%d-%Y-%m')) | |||
self.assertEqual( | |||
formatdate(test_date, 'dd-yyyy-MM'), | |||
test_date_obj.strftime('%d-%Y-%m')) | |||
def test_formatdate_forced_broken_locale(self): | |||
# Test with forced date formats | |||
lang = frappe.local.lang | |||
# Force fallback from Babel | |||
try: | |||
frappe.local.lang = 'FAKE' | |||
self.assertEqual( | |||
formatdate(test_date, 'dd-yyyy-mm'), | |||
test_date_obj.strftime('%d-%Y-%m')) | |||
self.assertEqual( | |||
formatdate(test_date, 'dd-yyyy-MM'), | |||
test_date_obj.strftime('%d-%Y-%m')) | |||
finally: | |||
frappe.local.lang = lang | |||
def test_format_date(self): | |||
# Test formatdate with various default date formats set | |||
for fmt, valid_fmt in test_date_formats.items(): | |||
frappe.db.set_default("date_format", fmt) | |||
frappe.local.user_date_format = None | |||
self.assertEqual(get_user_date_format(), fmt) | |||
self.assertEqual(formatdate(test_date), valid_fmt) | |||
# Test time formatters | |||
def test_format_time_forced(self): | |||
# Test with forced time formats | |||
self.assertEqual( | |||
format_time(test_time, 'ss:mm:HH'), | |||
test_date_obj.strftime('%S:%M:%H')) | |||
@unittest.expectedFailure | |||
def test_format_time_forced_broken_locale(self): | |||
# Test with forced time formats | |||
# Currently format_time defaults to HH:mm:ss if the locale is | |||
# broken, so this is an expected failure. | |||
lang = frappe.local.lang | |||
try: | |||
# Force fallback from Babel | |||
frappe.local.lang = 'FAKE' | |||
self.assertEqual( | |||
format_time(test_time, 'ss:mm:HH'), | |||
test_date_obj.strftime('%S:%M:%H')) | |||
finally: | |||
frappe.local.lang = lang | |||
def test_format_time(self): | |||
# Test format_time with various default time formats set | |||
for fmt, valid_fmt in test_time_formats.items(): | |||
frappe.db.set_default("time_format", fmt) | |||
frappe.local.user_time_format = None | |||
self.assertEqual(get_user_time_format(), fmt) | |||
self.assertEqual(format_time(test_time), valid_fmt) | |||
# Test datetime formatters | |||
def test_format_datetime_forced(self): | |||
# Test with forced date formats | |||
self.assertEqual( | |||
format_datetime(test_datetime, 'dd-yyyy-MM ss:mm:HH'), | |||
test_date_obj.strftime('%d-%Y-%m %S:%M:%H')) | |||
@unittest.expectedFailure | |||
def test_format_datetime_forced_broken_locale(self): | |||
# Test with forced datetime formats | |||
# Currently format_datetime defaults to yyyy-MM-dd HH:mm:ss | |||
# if the locale is broken, so this is an expected failure. | |||
lang = frappe.local.lang | |||
# Force fallback from Babel | |||
try: | |||
frappe.local.lang = 'FAKE' | |||
self.assertEqual( | |||
format_datetime(test_datetime, 'dd-yyyy-MM ss:mm:HH'), | |||
test_date_obj.strftime('%d-%Y-%m %S:%M:%H')) | |||
finally: | |||
frappe.local.lang = lang | |||
def test_format_datetime(self): | |||
# Test formatdate with various default date formats set | |||
for date_fmt, valid_date in test_date_formats.items(): | |||
frappe.db.set_default("date_format", date_fmt) | |||
frappe.local.user_date_format = None | |||
for time_fmt, valid_time in test_time_formats.items(): | |||
frappe.db.set_default("time_format", time_fmt) | |||
frappe.local.user_time_format = None | |||
valid_fmt = valid_date + ' ' + valid_time | |||
self.assertEqual( | |||
format_datetime(test_datetime), valid_fmt) |
@@ -209,22 +209,31 @@ def get_datetime_str(datetime_obj): | |||
datetime_obj = get_datetime(datetime_obj) | |||
return datetime_obj.strftime(DATETIME_FORMAT) | |||
def get_user_format(): | |||
if getattr(frappe.local, "user_format", None) is None: | |||
frappe.local.user_format = frappe.db.get_default("date_format") | |||
def get_user_date_format(): | |||
"""Get the current user date format. The result will be cached.""" | |||
if getattr(frappe.local, "user_date_format", None) is None: | |||
frappe.local.user_date_format = frappe.db.get_default("date_format") | |||
return frappe.local.user_format or "yyyy-mm-dd" | |||
return frappe.local.user_date_format or "yyyy-mm-dd" | |||
def formatdate(string_date=None, format_string=None): | |||
""" | |||
Converts the given string date to :data:`user_format` | |||
User format specified in defaults | |||
get_user_format = get_user_date_format # for backwards compatibility | |||
def get_user_time_format(): | |||
"""Get the current user time format. The result will be cached.""" | |||
if getattr(frappe.local, "user_time_format", None) is None: | |||
frappe.local.user_time_format = frappe.db.get_default("time_format") | |||
return frappe.local.user_time_format or "HH:mm:ss" | |||
def format_date(string_date=None, format_string=None): | |||
"""Converts the given string date to :data:`user_date_format` | |||
User format specified in defaults | |||
Examples: | |||
Examples: | |||
* dd-mm-yyyy | |||
* mm-dd-yyyy | |||
* dd/mm/yyyy | |||
* dd-mm-yyyy | |||
* mm-dd-yyyy | |||
* dd/mm/yyyy | |||
""" | |||
if not string_date: | |||
@@ -232,29 +241,60 @@ def formatdate(string_date=None, format_string=None): | |||
date = getdate(string_date) | |||
if not format_string: | |||
format_string = get_user_format() | |||
format_string = get_user_date_format() | |||
format_string = format_string.replace("mm", "MM") | |||
try: | |||
formatted_date = babel.dates.format_date(date, format_string, locale=(frappe.local.lang or "").replace("-", "_")) | |||
formatted_date = babel.dates.format_date( | |||
date, format_string, | |||
locale=(frappe.local.lang or "").replace("-", "_")) | |||
except UnknownLocaleError: | |||
format_string = format_string.replace("MM", "%m").replace("dd", "%d").replace("yyyy", "%Y") | |||
formatted_date = date.strftime(format_string) | |||
return formatted_date | |||
def format_time(txt): | |||
formatdate = format_date # For backwards compatibility | |||
def format_time(time_string=None, format_string=None): | |||
"""Converts the given string time to :data:`user_time_format` | |||
User format specified in defaults | |||
Examples: | |||
* HH:mm:ss | |||
* HH:mm | |||
""" | |||
if not time_string: | |||
return '' | |||
time_ = get_time(time_string) | |||
if not format_string: | |||
format_string = get_user_time_format() | |||
try: | |||
formatted_time = babel.dates.format_time(get_time(txt), locale=(frappe.local.lang or "").replace("-", "_")) | |||
formatted_time = babel.dates.format_time( | |||
time_, format_string, | |||
locale=(frappe.local.lang or "").replace("-", "_")) | |||
except UnknownLocaleError: | |||
formatted_time = get_time(txt).strftime("%H:%M:%S") | |||
formatted_time = time_.strftime("%H:%M:%S") | |||
return formatted_time | |||
def format_datetime(datetime_string, format_string=None): | |||
"""Converts the given string time to :data:`user_datetime_format` | |||
User format specified in defaults | |||
Examples: | |||
* dd-mm-yyyy HH:mm:ss | |||
* mm-dd-yyyy HH:mm | |||
""" | |||
if not datetime_string: | |||
return | |||
datetime = get_datetime(datetime_string) | |||
if not format_string: | |||
format_string = get_user_format().replace("mm", "MM") + " HH:mm:ss" | |||
format_string = ( | |||
get_user_date_format().replace("mm", "MM") | |||
+ ' ' + get_user_time_format()) | |||
try: | |||
formatted_datetime = babel.dates.format_datetime(datetime, format_string, locale=(frappe.local.lang or "").replace("-", "_")) | |||
@@ -363,14 +403,14 @@ def rounded(num, precision=0): | |||
# avoid rounding errors | |||
num = round(num * multiplier if precision else num, 8) | |||
floor = math.floor(num) | |||
decimal_part = num - floor | |||
floor_num = math.floor(num) | |||
decimal_part = num - floor_num | |||
if not precision and decimal_part == 0.5: | |||
num = floor if (floor % 2 == 0) else floor + 1 | |||
num = floor_num if (floor_num % 2 == 0) else floor_num + 1 | |||
else: | |||
if decimal_part == 0.5: | |||
num = floor + 1 | |||
num = floor_num + 1 | |||
else: | |||
num = round(num) | |||
@@ -146,6 +146,7 @@ def add_country_and_currency(name, country): | |||
"country_name": name, | |||
"code": country.code, | |||
"date_format": country.date_format or "dd-mm-yyyy", | |||
"time_format": country.time_format or "HH:mm:ss", | |||
"time_zones": "\n".join(country.timezones or []), | |||
"docstatus": 0 | |||
}).db_insert() | |||
@@ -32,8 +32,10 @@ def get_safe_globals(): | |||
datautils = frappe._dict() | |||
if frappe.db: | |||
date_format = frappe.db.get_default("date_format") or "yyyy-mm-dd" | |||
time_format = frappe.db.get_default("time_format") or "HH:mm:ss" | |||
else: | |||
date_format = 'yyyy-mm-dd' | |||
date_format = "yyyy-mm-dd" | |||
time_format = "HH:mm:ss" | |||
add_module_properties(frappe.utils.data, datautils, lambda obj: hasattr(obj, "__call__")) | |||
@@ -44,54 +46,55 @@ def get_safe_globals(): | |||
out = frappe._dict( | |||
# make available limited methods of frappe | |||
json = json, | |||
dict = dict, | |||
frappe = frappe._dict( | |||
_ = frappe._, | |||
_dict = frappe._dict, | |||
flags = frappe.flags, | |||
format = frappe.format_value, | |||
format_value = frappe.format_value, | |||
date_format = date_format, | |||
format_date = frappe.utils.data.global_date_format, | |||
form_dict = getattr(frappe.local, 'form_dict', {}), | |||
get_meta = frappe.get_meta, | |||
get_doc = frappe.get_doc, | |||
get_cached_doc = frappe.get_cached_doc, | |||
get_list = frappe.get_list, | |||
get_all = frappe.get_all, | |||
get_system_settings = frappe.get_system_settings, | |||
utils = datautils, | |||
get_url = frappe.utils.get_url, | |||
render_template = frappe.render_template, | |||
msgprint = frappe.msgprint, | |||
user = user, | |||
get_fullname = frappe.utils.get_fullname, | |||
get_gravatar = frappe.utils.get_gravatar_url, | |||
full_name = frappe.local.session.data.full_name if getattr(frappe.local, "session", None) else "Guest", | |||
request = getattr(frappe.local, 'request', {}), | |||
session = frappe._dict( | |||
user = user, | |||
csrf_token = frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else '' | |||
json=json, | |||
dict=dict, | |||
frappe=frappe._dict( | |||
_=frappe._, | |||
_dict=frappe._dict, | |||
flags=frappe.flags, | |||
format=frappe.format_value, | |||
format_value=frappe.format_value, | |||
date_format=date_format, | |||
time_format=time_format, | |||
format_date=frappe.utils.data.global_date_format, | |||
form_dict=getattr(frappe.local, 'form_dict', {}), | |||
get_meta=frappe.get_meta, | |||
get_doc=frappe.get_doc, | |||
get_cached_doc=frappe.get_cached_doc, | |||
get_list=frappe.get_list, | |||
get_all=frappe.get_all, | |||
get_system_settings=frappe.get_system_settings, | |||
utils=datautils, | |||
get_url=frappe.utils.get_url, | |||
render_template=frappe.render_template, | |||
msgprint=frappe.msgprint, | |||
user=user, | |||
get_fullname=frappe.utils.get_fullname, | |||
get_gravatar=frappe.utils.get_gravatar_url, | |||
full_name=frappe.local.session.data.full_name if getattr(frappe.local, "session", None) else "Guest", | |||
request=getattr(frappe.local, 'request', {}), | |||
session=frappe._dict( | |||
user=user, | |||
csrf_token=frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else '' | |||
), | |||
socketio_port = frappe.conf.socketio_port, | |||
get_hooks = frappe.get_hooks, | |||
socketio_port=frappe.conf.socketio_port, | |||
get_hooks=frappe.get_hooks, | |||
), | |||
style = frappe._dict( | |||
border_color = '#d1d8dd' | |||
style=frappe._dict( | |||
border_color='#d1d8dd' | |||
), | |||
get_toc = get_toc, | |||
get_next_link = get_next_link, | |||
_ = frappe._, | |||
get_shade = get_shade, | |||
scrub = scrub, | |||
guess_mimetype = mimetypes.guess_type, | |||
html2text = html2text, | |||
dev_server = 1 if os.environ.get('DEV_SERVER', False) else 0 | |||
get_toc=get_toc, | |||
get_next_link=get_next_link, | |||
_=frappe._, | |||
get_shade=get_shade, | |||
scrub=scrub, | |||
guess_mimetype=mimetypes.guess_type, | |||
html2text=html2text, | |||
dev_server=1 if os.environ.get('DEV_SERVER', False) else 0 | |||
) | |||
add_module_properties(frappe.exceptions, out.frappe, lambda obj: inspect.isclass(obj) and issubclass(obj, Exception)) | |||
@@ -99,6 +102,7 @@ def get_safe_globals(): | |||
if not frappe.flags.in_setup_help: | |||
out.get_visible_columns = get_visible_columns | |||
out.frappe.date_format = date_format | |||
out.frappe.time_format = time_format | |||
out.frappe.db = frappe._dict( | |||
get_list = frappe.get_list, | |||
get_all = frappe.get_all, | |||
@@ -19,6 +19,7 @@ | |||
"homepage": "https://frappe.io", | |||
"dependencies": { | |||
"ace-builds": "^1.4.1", | |||
"air-datepicker": "http://github.com/frappe/air-datepicker", | |||
"awesomplete": "^1.1.2", | |||
"bootstrap": "^4.3.1", | |||
"cookie": "^0.3.1", | |||
@@ -210,6 +210,12 @@ after@0.8.2: | |||
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" | |||
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= | |||
"air-datepicker@http://github.com/frappe/air-datepicker": | |||
version "2.2.3" | |||
resolved "http://github.com/frappe/air-datepicker#ed37b94d95c68d8544357e330be0c89d044a3eea" | |||
dependencies: | |||
jquery ">=2.0.0 <4.0.0" | |||
ajv@^5.1.0: | |||
version "5.5.2" | |||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" | |||
@@ -2560,6 +2566,11 @@ jpeg-js@^0.3.2: | |||
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.6.tgz#c40382aac9506e7d1f2d856eb02f6c7b2a98b37c" | |||
integrity sha512-MUj2XlMB8kpe+8DJUGH/3UJm4XpI8XEgZQ+CiHDeyrGoKPdW/8FJv6ku+3UiYm5Fz3CWaL+iXmD8Q4Ap6aC1Jw== | |||
"jquery@>=2.0.0 <4.0.0": | |||
version "3.4.1" | |||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" | |||
integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== | |||
js-base64@^2.1.8, js-base64@^2.1.9: | |||
version "2.5.1" | |||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" | |||
@@ -5356,9 +5367,9 @@ yargs-parser@^5.0.0: | |||
camelcase "^3.0.0" | |||
yargs@^14.2: | |||
version "14.2.0" | |||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.0.tgz#f116a9242c4ed8668790b40759b4906c276e76c3" | |||
integrity sha512-/is78VKbKs70bVZH7w4YaZea6xcJWOAwkhbR0CFuZBmYtfTYF0xjGJF43AYd8g2Uii1yJwmS5GR2vBmrc32sbg== | |||
version "14.2.2" | |||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.2.tgz#2769564379009ff8597cdd38fba09da9b493c4b5" | |||
integrity sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA== | |||
dependencies: | |||
cliui "^5.0.0" | |||
decamelize "^1.2.0" | |||