From 1952920add38964450ae12121e3f82f628780be8 Mon Sep 17 00:00:00 2001 From: hrwx Date: Tue, 15 Jun 2021 22:53:24 +0530 Subject: [PATCH 01/20] feat: Convert datetime field values to system timezone Co-authored-by: Sahil Khan --- frappe/boot.py | 12 +++- frappe/core/doctype/user/user.js | 7 ++- frappe/core/doctype/user/user.py | 8 +++ .../js/frappe/form/controls/datetime.js | 44 ++++++++++++- frappe/public/js/frappe/utils/datetime.js | 61 +++++++++++-------- 5 files changed, 101 insertions(+), 31 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 0589e32ac8..feace1a66d 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -17,6 +17,7 @@ from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_p from frappe.model.base_document import get_controller from frappe.social.doctype.post.post import frequently_visited_links from frappe.core.doctype.navbar_settings.navbar_settings import get_navbar_settings, get_app_logo +from frappe.utils import get_time_zone def get_bootinfo(): """build and return boot info""" @@ -58,6 +59,7 @@ def get_bootinfo(): bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1}) bootinfo.navbar_settings = get_navbar_settings() bootinfo.notification_settings = get_notification_settings() + set_time_zone(bootinfo) # ipinfo if frappe.session.data.get('ipinfo'): @@ -220,8 +222,8 @@ def load_translations(bootinfo): bootinfo["__messages"] = messages def get_user_info(): - user_info = frappe.db.get_all('User', fields=['`name`', 'full_name as fullname', 'user_image as image', - 'gender', 'email', 'username', 'bio', 'location', 'interest', 'banner_image', 'allowed_in_mentions', 'user_type'], + user_info = frappe.db.get_all('User', fields=['`name`', 'full_name as fullname', 'user_image as image', 'gender', + 'email', 'username', 'bio', 'location', 'interest', 'banner_image', 'allowed_in_mentions', 'user_type', 'time_zone'], filters=dict(enabled=1)) user_info_map = {d.name: d for d in user_info} @@ -324,3 +326,9 @@ def get_desk_settings(): def get_notification_settings(): return frappe.get_cached_doc('Notification Settings', frappe.session.user) + +def set_time_zone(bootinfo): + bootinfo.time_zone = { + "system_time_zone": get_time_zone(), + "user_time_zone": bootinfo.get("user_info", {}).get(frappe.session.user, {}).get("time_zone", None) or get_time_zone() + } diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 8c5b89c5fc..7c9e00d6bc 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -77,7 +77,12 @@ frappe.ui.form.on('User', { } }, refresh: function(frm) { - var doc = frm.doc; + let doc = frm.doc; + + if (frm.is_new()) { + frm.set_value("time_zone", frappe.sys_defaults.time_zone); + } + if (in_list(['System User', 'Website User'], frm.doc.user_type) && !frm.is_new() && !frm.roles_editor && frm.can_edit_roles) { frm.reload_doc(); diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 13063b8fd2..93963c6015 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -80,6 +80,7 @@ class User(Document): self.validate_roles() self.validate_allowed_modules() self.validate_user_image() + self.set_time_zone() if self.language == "Loading...": self.language = None @@ -594,6 +595,13 @@ class User(Document): return user + def set_time_zone(self): + from frappe.utils import get_time_zone + + if not self.time_zone: + frappe.msgprint(_("User Time Zone was not set, defaulting to System Time Zone."), title=_("User Time Zone")) + self.time_zone = get_time_zone() + @frappe.whitelist() def get_timezones(): import pytz diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index 341a933066..c634aac293 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -1,4 +1,16 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.ControlDate { + set_formatted_input(value) { + if (this.timepicker_only) return; + if (!this.datepicker) return; + if (!value) { + this.datepicker.clear(); + return; + } else if (value === "Today") { + value = this.get_now_date(); + } + + this.$input && this.$input.val(this.format_for_input(value)); + } set_date_options() { super.set_date_options(); this.today_text = __("Now"); @@ -14,10 +26,36 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co get_now_date() { return frappe.datetime.now_datetime(true); } + parse(value) { + if (value) { + value = frappe.datetime.user_to_str(value, false); + + if (!frappe.datetime.is_timezone_same()) { + value = frappe.datetime.convert_to_system_tz(value, true); + } + + return value; + } + } + format_for_input(value) { + if (!value) return ""; + + let m = frappe.datetime.is_timezone_same(); + if (!frappe.datetime.is_timezone_same()) { + m = frappe.datetime.convert_to_user_tz(value, true) + value = frappe.datetime.convert_to_user_tz(value, true); + } + + return frappe.datetime.str_to_user(value, false); + } set_description() { - const { description } = this.df; - const { time_zone } = frappe.sys_defaults; - if (!this.df.hide_timezone && !frappe.datetime.is_timezone_same()) { + const description = this.df.description; + const time_zone = frappe.boot.time_zone ? frappe.boot.time_zone.user_time_zone : frappe.sys_defaults.time_zone; + + if (!this.df.hide_timezone) { + // Always show the timezone when rendering the Datetime field since the datetime value will + // always be in system_time_zone rather then local time. + if (!description) { this.df.description = time_zone; } else if (!description.includes(time_zone)) { diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js index 0c6fea2986..3d00762c94 100644 --- a/frappe/public/js/frappe/utils/datetime.js +++ b/frappe/public/js/frappe/utils/datetime.js @@ -13,33 +13,44 @@ frappe.provide("frappe.datetime"); $.extend(frappe.datetime, { convert_to_user_tz: function(date, format) { // format defaults to true - if(frappe.sys_defaults.time_zone) { - var date_obj = moment.tz(date, frappe.sys_defaults.time_zone).local(); + // Converts the datetime string to system time zone first since the database only stores datetime in + // system time zone and then convert the string to user time zone(from User doctype). + let date_obj = null; + if (frappe.boot.time_zone && frappe.boot.time_zone.system_time_zone && frappe.boot.time_zone.user_time_zone) { + date_obj = moment.tz(date, frappe.boot.time_zone.system_time_zone) + .clone() + .tz(frappe.boot.time_zone.user_time_zone); } else { - var date_obj = moment(date); + date_obj = moment(date); } - return (format===false) ? date_obj : date_obj.format(frappe.defaultDatetimeFormat); + return format===false ? date_obj : date_obj.format(frappe.defaultDatetimeFormat); }, convert_to_system_tz: function(date, format) { // format defaults to true - - if(frappe.sys_defaults.time_zone) { - var date_obj = moment(date).tz(frappe.sys_defaults.time_zone); + // Converts the datetime string to user time zone (from User doctype) first since this fn is called in datetime which accepts datetime + // in user time zone then convert the string to user time zone. + // This is done so that only one timezone is present in database and we do not end up storing local timezone since it changes + // as per the location of user. + let date_obj = null; + if (frappe.boot.time_zone && frappe.boot.time_zone.system_time_zone && frappe.boot.time_zone.user_time_zone) { + date_obj = moment.tz(date, frappe.boot.time_zone.user_time_zone) + .clone() + .tz(frappe.boot.time_zone.system_time_zone); } else { - var date_obj = moment(date); + date_obj = moment(date); } - return (format===false) ? date_obj : date_obj.format(frappe.defaultDatetimeFormat); + return format===false ? date_obj : date_obj.format(frappe.defaultDatetimeFormat); }, is_timezone_same: function() { - if(frappe.sys_defaults.time_zone) { - return moment().tz(frappe.sys_defaults.time_zone).utcOffset() === moment().utcOffset(); - } else { - return true; + if (frappe.boot.time_zone && frappe.boot.time_zone.system_time_zone && frappe.boot.time_zone.user_time_zone) { + return moment().tz(frappe.boot.time_zone.system_time_zone).utcOffset() === moment().tz(frappe.boot.time_zone.user_time_zone).utcOffset(); } + + return true; }, str_to_obj: function(d) { @@ -186,18 +197,18 @@ $.extend(frappe.datetime, { }, _date: function(format, as_obj = false) { - const time_zone = frappe.sys_defaults && frappe.sys_defaults.time_zone; - let date; - if (time_zone) { - date = moment.tz(time_zone); - } else { - date = moment(); - } - if (as_obj) { - return frappe.datetime.moment_to_date_obj(date); - } else { - return date.format(format); - } + /** + * Whenever we are getting now_date/datetime, always make sure dates are fetched using usertime zone. + * This is to make sure that time is as per user time zone set in User doctype, If a user had to change the timezone, + * we will end up having multiple timezone by not honouring timezone in User doctype. + * This will make sure that at any point we know which timezone the user if following and not have random timezone + * when the timezone of the local machine changes. + */ + let time_zone = frappe.boot.time_zone.user_time_zone || frappe.boot.time_zone.system_time_zone; + if (!time_zone) time_zone = frappe.sys_defaults.time_zone; + + let date = moment.tz(time_zone); + return as_obj ? frappe.datetime.moment_to_date_obj(date) : date.format(format); }, moment_to_date_obj: function(moment) { From 02640b791f24a9d089e8a65852bcf2ca0f12bf9e Mon Sep 17 00:00:00 2001 From: hrwx Date: Tue, 15 Jun 2021 23:31:30 +0530 Subject: [PATCH 02/20] fix: check if frappe.sys_defaults.time_zone exists --- .../core/doctype/system_settings/system_settings.js | 6 ++++++ frappe/core/doctype/user/user.js | 6 ++++++ frappe/core/doctype/user/user.py | 1 - frappe/public/js/frappe/form/controls/datetime.js | 11 ++++++----- frappe/public/js/frappe/form/controls/time.js | 2 +- frappe/public/js/frappe/form/formatters.js | 8 ++------ frappe/public/js/frappe/utils/datetime.js | 7 +++---- 7 files changed, 24 insertions(+), 17 deletions(-) diff --git a/frappe/core/doctype/system_settings/system_settings.js b/frappe/core/doctype/system_settings/system_settings.js index c0c9074cbc..aefe3786bd 100644 --- a/frappe/core/doctype/system_settings/system_settings.js +++ b/frappe/core/doctype/system_settings/system_settings.js @@ -32,5 +32,11 @@ frappe.ui.form.on("System Settings", { frm.set_value('prepared_report_expiry_period', 7); } } + }, + after_save: function(frm) { + if (frappe.boot.time_zone && frappe.boot.time_zone.system_time_zone !== frm.doc.time_zone) { + // Clear cache after saving to refresh the values of time_zone + frappe.ui.toolbar.clear_cache(); + } } }); diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 7c9e00d6bc..819684cdfe 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -271,6 +271,12 @@ frappe.ui.form.on('User', { } } }); + }, + after_save: function(frm) { + if (frappe.boot.time_zone && frappe.boot.time_zone.user_time_zone !== frm.doc.time_zone) { + // Clear cache after saving to refresh the values of time_zone + frappe.ui.toolbar.clear_cache(); + } } }); diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 93963c6015..cf2b045c6d 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -599,7 +599,6 @@ class User(Document): from frappe.utils import get_time_zone if not self.time_zone: - frappe.msgprint(_("User Time Zone was not set, defaulting to System Time Zone."), title=_("User Time Zone")) self.time_zone = get_time_zone() @frappe.whitelist() diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index c634aac293..a8b19c4ece 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -30,7 +30,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co if (value) { value = frappe.datetime.user_to_str(value, false); - if (!frappe.datetime.is_timezone_same()) { + if (!frappe.datetime.is_system_time_zone()) { value = frappe.datetime.convert_to_system_tz(value, true); } @@ -40,9 +40,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co format_for_input(value) { if (!value) return ""; - let m = frappe.datetime.is_timezone_same(); - if (!frappe.datetime.is_timezone_same()) { - m = frappe.datetime.convert_to_user_tz(value, true) + if (!frappe.datetime.is_system_time_zone()) { value = frappe.datetime.convert_to_user_tz(value, true); } @@ -50,7 +48,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co } set_description() { const description = this.df.description; - const time_zone = frappe.boot.time_zone ? frappe.boot.time_zone.user_time_zone : frappe.sys_defaults.time_zone; + const time_zone = this.get_user_time_zone(); if (!this.df.hide_timezone) { // Always show the timezone when rendering the Datetime field since the datetime value will @@ -64,6 +62,9 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co } super.set_description(); } + get_user_time_zone() { + return frappe.boot.time_zone ? frappe.boot.time_zone.user_time_zone : frappe.sys_defaults.time_zone; + } set_datepicker() { super.set_datepicker(); if (this.datepicker.opts.timeFormat.indexOf('s') == -1) { diff --git a/frappe/public/js/frappe/form/controls/time.js b/frappe/public/js/frappe/form/controls/time.js index a7b6645681..f7fcc4c618 100644 --- a/frappe/public/js/frappe/form/controls/time.js +++ b/frappe/public/js/frappe/form/controls/time.js @@ -71,7 +71,7 @@ frappe.ui.form.ControlTime = class ControlTime extends frappe.ui.form.ControlDat set_description() { const { description } = this.df; const { time_zone } = frappe.sys_defaults; - if (!frappe.datetime.is_timezone_same()) { + if (!frappe.datetime.is_system_time_zone()) { if (!description) { this.df.description = time_zone; } else if (!description.includes(time_zone)) { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index b9a838688d..caecd65336 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -167,12 +167,8 @@ frappe.form.formatters = { }, Datetime: function(value) { if(value) { - var m = moment(frappe.datetime.convert_to_user_tz(value)); - if(frappe.boot.sysdefaults.time_zone) { - m = m.tz(frappe.boot.sysdefaults.time_zone); - } - return m.format(frappe.boot.sysdefaults.date_format.toUpperCase() - + ' ' + frappe.boot.sysdefaults.time_format); + return moment(frappe.datetime.convert_to_user_tz(value)) + .format(frappe.boot.sysdefaults.date_format.toUpperCase() + ' ' + frappe.boot.sysdefaults.time_format); } else { return ""; } diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js index 3d00762c94..ae7308cf7b 100644 --- a/frappe/public/js/frappe/utils/datetime.js +++ b/frappe/public/js/frappe/utils/datetime.js @@ -45,7 +45,7 @@ $.extend(frappe.datetime, { return format===false ? date_obj : date_obj.format(frappe.defaultDatetimeFormat); }, - is_timezone_same: function() { + is_system_time_zone: function() { if (frappe.boot.time_zone && frappe.boot.time_zone.system_time_zone && frappe.boot.time_zone.user_time_zone) { return moment().tz(frappe.boot.time_zone.system_time_zone).utcOffset() === moment().tz(frappe.boot.time_zone.user_time_zone).utcOffset(); } @@ -204,10 +204,9 @@ $.extend(frappe.datetime, { * This will make sure that at any point we know which timezone the user if following and not have random timezone * when the timezone of the local machine changes. */ - let time_zone = frappe.boot.time_zone.user_time_zone || frappe.boot.time_zone.system_time_zone; - if (!time_zone) time_zone = frappe.sys_defaults.time_zone; - + let time_zone = frappe.boot.time_zone ? frappe.boot.time_zone.user_time_zone || frappe.boot.time_zone.system_time_zone : frappe.sys_defaults.time_zone; let date = moment.tz(time_zone); + return as_obj ? frappe.datetime.moment_to_date_obj(date) : date.format(format); }, From 1f70c27e9f3ec11ce04bd857d7cae6f2ff2356bc Mon Sep 17 00:00:00 2001 From: hrwx Date: Mon, 15 Nov 2021 14:33:28 +0000 Subject: [PATCH 03/20] chore: rename timezone keys --- frappe/boot.py | 4 ++-- .../system_settings/system_settings.js | 6 ++--- frappe/core/doctype/user/user.js | 6 ++--- frappe/core/doctype/user/user.py | 14 +++++------- .../js/frappe/form/controls/datetime.js | 2 +- frappe/public/js/frappe/utils/datetime.js | 22 +++++++++++-------- 6 files changed, 26 insertions(+), 28 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index fd7564d75a..e671d8b37d 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -329,6 +329,6 @@ def get_notification_settings(): def set_time_zone(bootinfo): bootinfo.time_zone = { - "system_time_zone": get_time_zone(), - "user_time_zone": bootinfo.get("user_info", {}).get(frappe.session.user, {}).get("time_zone", None) or get_time_zone() + "system": get_time_zone(), + "user": bootinfo.get("user_info", {}).get(frappe.session.user, {}).get("time_zone", None) or get_time_zone() } diff --git a/frappe/core/doctype/system_settings/system_settings.js b/frappe/core/doctype/system_settings/system_settings.js index aefe3786bd..0164a1a683 100644 --- a/frappe/core/doctype/system_settings/system_settings.js +++ b/frappe/core/doctype/system_settings/system_settings.js @@ -34,9 +34,7 @@ frappe.ui.form.on("System Settings", { } }, after_save: function(frm) { - if (frappe.boot.time_zone && frappe.boot.time_zone.system_time_zone !== frm.doc.time_zone) { - // Clear cache after saving to refresh the values of time_zone - frappe.ui.toolbar.clear_cache(); - } + // Clear cache after saving to refresh the values of boot. + frappe.ui.toolbar.clear_cache(); } }); diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 48dc2d1672..681080b2b3 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -274,10 +274,8 @@ frappe.ui.form.on('User', { }); }, after_save: function(frm) { - if (frappe.boot.time_zone && frappe.boot.time_zone.user_time_zone !== frm.doc.time_zone) { - // Clear cache after saving to refresh the values of time_zone - frappe.ui.toolbar.clear_cache(); - } + // Clear cache after saving to refresh the values of boot. + frappe.ui.toolbar.clear_cache(); } }); diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index fd19f4d82e..76bdbbbeb8 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -7,7 +7,7 @@ import frappe.defaults import frappe.permissions from frappe.model.document import Document from frappe.utils import (cint, flt, has_gravatar, escape_html, format_datetime, - now_datetime, get_formatted_email, today) + now_datetime, get_formatted_email, today, get_time_zone) from frappe import throw, msgprint, _ from frappe.utils.password import update_password as _update_password, check_password, get_password_reset_limit from frappe.desk.notifications import clear_notifications @@ -231,11 +231,11 @@ class User(Document): def validate_share(self, docshare): pass # if docshare.user == self.name: - # if self.user_type=="System User": - # if docshare.share != 1: - # frappe.throw(_("Sorry! User should have complete access to their own record.")) - # else: - # frappe.throw(_("Sorry! Sharing with Website User is prohibited.")) + # if self.user_type=="System User": + # if docshare.share != 1: + # frappe.throw(_("Sorry! User should have complete access to their own record.")) + # else: + # frappe.throw(_("Sorry! Sharing with Website User is prohibited.")) def send_password_notification(self, new_password): try: @@ -592,8 +592,6 @@ class User(Document): return user def set_time_zone(self): - from frappe.utils import get_time_zone - if not self.time_zone: self.time_zone = get_time_zone() diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index b69f40e9c4..3142f1bf0f 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -63,7 +63,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co super.set_description(); } get_user_time_zone() { - return frappe.boot.time_zone ? frappe.boot.time_zone.user_time_zone : frappe.sys_defaults.time_zone; + return frappe.boot.time_zone ? frappe.boot.time_zone.user : frappe.sys_defaults.time_zone; } set_datepicker() { super.set_datepicker(); diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js index 71fdbbb897..c85cbd42e7 100644 --- a/frappe/public/js/frappe/utils/datetime.js +++ b/frappe/public/js/frappe/utils/datetime.js @@ -16,10 +16,10 @@ $.extend(frappe.datetime, { // Converts the datetime string to system time zone first since the database only stores datetime in // system time zone and then convert the string to user time zone(from User doctype). let date_obj = null; - if (frappe.boot.time_zone && frappe.boot.time_zone.system_time_zone && frappe.boot.time_zone.user_time_zone) { - date_obj = moment.tz(date, frappe.boot.time_zone.system_time_zone) + if (frappe.boot.time_zone && frappe.boot.time_zone.system && frappe.boot.time_zone.user) { + date_obj = moment.tz(date, frappe.boot.time_zone.system) .clone() - .tz(frappe.boot.time_zone.user_time_zone); + .tz(frappe.boot.time_zone.user); } else { date_obj = moment(date); } @@ -34,10 +34,10 @@ $.extend(frappe.datetime, { // This is done so that only one timezone is present in database and we do not end up storing local timezone since it changes // as per the location of user. let date_obj = null; - if (frappe.boot.time_zone && frappe.boot.time_zone.system_time_zone && frappe.boot.time_zone.user_time_zone) { - date_obj = moment.tz(date, frappe.boot.time_zone.user_time_zone) + if (frappe.boot.time_zone && frappe.boot.time_zone.system && frappe.boot.time_zone.user) { + date_obj = moment.tz(date, frappe.boot.time_zone.user) .clone() - .tz(frappe.boot.time_zone.system_time_zone); + .tz(frappe.boot.time_zone.system); } else { date_obj = moment(date); } @@ -46,13 +46,17 @@ $.extend(frappe.datetime, { }, is_system_time_zone: function() { - if (frappe.boot.time_zone && frappe.boot.time_zone.system_time_zone && frappe.boot.time_zone.user_time_zone) { - return moment().tz(frappe.boot.time_zone.system_time_zone).utcOffset() === moment().tz(frappe.boot.time_zone.user_time_zone).utcOffset(); + if (frappe.boot.time_zone && frappe.boot.time_zone.system && frappe.boot.time_zone.user) { + return moment().tz(frappe.boot.time_zone.system).utcOffset() === moment().tz(frappe.boot.time_zone.user).utcOffset(); } return true; }, + is_timezone_same: function() { + return frappe.datetime.is_system_time_zone(); + }, + str_to_obj: function(d) { return moment(d, frappe.defaultDatetimeFormat)._d; }, @@ -204,7 +208,7 @@ $.extend(frappe.datetime, { * This will make sure that at any point we know which timezone the user if following and not have random timezone * when the timezone of the local machine changes. */ - let time_zone = frappe.boot.time_zone ? frappe.boot.time_zone.user_time_zone || frappe.boot.time_zone.system_time_zone : frappe.sys_defaults.time_zone; + let time_zone = frappe.boot.time_zone ? frappe.boot.time_zone.user || frappe.boot.time_zone.system : frappe.sys_defaults.time_zone; let date = moment.tz(time_zone); return as_obj ? frappe.datetime.moment_to_date_obj(date) : date.format(format); From 576efed7f5a9cd29d01e746bcd506bde4292a811 Mon Sep 17 00:00:00 2001 From: hrwx Date: Mon, 15 Nov 2021 14:35:56 +0000 Subject: [PATCH 04/20] chore: move timezone clear cache to on_update --- frappe/core/doctype/system_settings/system_settings.js | 8 +++++--- frappe/core/doctype/user/user.js | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/system_settings/system_settings.js b/frappe/core/doctype/system_settings/system_settings.js index 0164a1a683..4eeab0274b 100644 --- a/frappe/core/doctype/system_settings/system_settings.js +++ b/frappe/core/doctype/system_settings/system_settings.js @@ -33,8 +33,10 @@ frappe.ui.form.on("System Settings", { } } }, - after_save: function(frm) { - // Clear cache after saving to refresh the values of boot. - frappe.ui.toolbar.clear_cache(); + on_update: function(frm) { + if (frappe.boot.time_zone && frappe.boot.time_zone.system !== frm.doc.time_zone) { + // Clear cache after saving to refresh the values of boot. + frappe.ui.toolbar.clear_cache(); + } } }); diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 681080b2b3..79c2665a05 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -273,9 +273,11 @@ frappe.ui.form.on('User', { } }); }, - after_save: function(frm) { - // Clear cache after saving to refresh the values of boot. - frappe.ui.toolbar.clear_cache(); + on_update: function(frm) { + if (frappe.boot.time_zone && frappe.boot.time_zone.user !== frm.doc.time_zone) { + // Clear cache after saving to refresh the values of boot. + frappe.ui.toolbar.clear_cache(); + } } }); From e751b07bde74046aeba9520ffd843f66e28336dc Mon Sep 17 00:00:00 2001 From: hrwx Date: Mon, 29 Nov 2021 16:49:46 +0000 Subject: [PATCH 05/20] chore: make system timezone readonly --- frappe/core/doctype/system_settings/system_settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 82e88d2477..2a06f58845 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -95,6 +95,7 @@ "fieldname": "time_zone", "fieldtype": "Select", "label": "Time Zone", + "read_only": 1, "reqd": 1 }, { @@ -467,7 +468,7 @@ "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2021-10-21 19:24:15.232430", + "modified": "2021-11-29 17:49:20.950033", "modified_by": "Administrator", "module": "Core", "name": "System Settings", From 67b0293e6fc20eb449c79e26f67c4eb4d9341d46 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 30 Nov 2021 12:33:29 +0530 Subject: [PATCH 06/20] fix: Pretty date rendering based on user-timezone --- frappe/public/js/frappe/utils/datetime.js | 25 +++++++++++--------- frappe/public/js/frappe/utils/pretty_date.js | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js index c85cbd42e7..165636bad4 100644 --- a/frappe/public/js/frappe/utils/datetime.js +++ b/frappe/public/js/frappe/utils/datetime.js @@ -134,20 +134,23 @@ $.extend(frappe.datetime, { }, str_to_user: function(val, only_time = false) { - if(!val) return ""; + if (!val) return ""; + const user_time_fmt = frappe.datetime.get_user_time_fmt(); + let date_obj = moment(val); + let user_format = user_time_fmt; - var user_time_fmt = frappe.datetime.get_user_time_fmt(); - if(only_time) { - return moment(val, frappe.defaultTimeFormat) - .format(user_time_fmt); - } - - var user_date_fmt = frappe.datetime.get_user_date_fmt().toUpperCase(); - if(typeof val !== "string" || val.indexOf(" ")===-1) { - return moment(val).format(user_date_fmt); + if (only_time) { + date_obj = moment(val, frappe.defaultTimeFormat); } else { - return moment(val, "YYYY-MM-DD HH:mm:ss").format(user_date_fmt + " " + user_time_fmt); + let user_date_fmt = frappe.datetime.get_user_date_fmt().toUpperCase(); + if (typeof val !== "string" || val.indexOf(" ")===-1) { + date_obj = moment(val); + } else { + date_obj = moment(val, "YYYY-MM-DD HH:mm:ss"); + user_format = user_date_fmt + " " + user_time_fmt; + } } + return date_obj.tz(frappe.boot.time_zone.user).format(user_format); }, get_datetime_as_string: function(d) { diff --git a/frappe/public/js/frappe/utils/pretty_date.js b/frappe/public/js/frappe/utils/pretty_date.js index 3ebe2c1ae2..a5279682ce 100644 --- a/frappe/public/js/frappe/utils/pretty_date.js +++ b/frappe/public/js/frappe/utils/pretty_date.js @@ -6,7 +6,7 @@ function prettyDate(date, mini) { date = new Date((date || "").replace(/-/g, "/").replace(/[TZ]/g, " ").replace(/\.[0-9]*/, "")); } - let diff = (((new Date()).getTime() - date.getTime()) / 1000); + let diff = (((new Date(frappe.datetime.now_datetime())).getTime() - date.getTime()) / 1000); let day_diff = Math.floor(diff / 86400); if (isNaN(day_diff) || day_diff < 0) return ''; From dc522395c80fa9e320b63bf23827974ef0ef3488 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 30 Nov 2021 16:49:27 +0530 Subject: [PATCH 07/20] test: Fix test case for datetime --- cypress/integration/datetime.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cypress/integration/datetime.js b/cypress/integration/datetime.js index b310526c7c..ef1952dc94 100644 --- a/cypress/integration/datetime.js +++ b/cypress/integration/datetime.js @@ -92,15 +92,15 @@ context('Control Date, Time and DateTime', () => { 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' + doc_value: '2019-12-02 00:30:12', // system timezone (America/New_York) + input_value: '02.12.2019 11:00:12' // admin timezone (Asia/Kolkata) }, { 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' + doc_value: '2019-12-02 00:30:00', // system timezone (America/New_York) + input_value: '12-02-2019 11:00' // admin timezone (Asia/Kolkata) } ]; datetime_formats.forEach(d => { From a86f8d9640a7d01f88cc2d2beeac52572c00aa98 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 1 Dec 2021 11:27:23 +0530 Subject: [PATCH 08/20] fix: Do not guess timezone for only time - Time value should be consistent across timezones - Only worry about timezone when dealing with datetime --- frappe/public/js/frappe/utils/datetime.js | 26 ++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js index 165636bad4..2fcfd75c5e 100644 --- a/frappe/public/js/frappe/utils/datetime.js +++ b/frappe/public/js/frappe/utils/datetime.js @@ -17,14 +17,15 @@ $.extend(frappe.datetime, { // system time zone and then convert the string to user time zone(from User doctype). let date_obj = null; if (frappe.boot.time_zone && frappe.boot.time_zone.system && frappe.boot.time_zone.user) { - date_obj = moment.tz(date, frappe.boot.time_zone.system) + date_obj = moment(date) + .tz(frappe.boot.time_zone.system) .clone() .tz(frappe.boot.time_zone.user); } else { date_obj = moment(date); } - return format===false ? date_obj : date_obj.format(frappe.defaultDatetimeFormat); + return format === false ? date_obj : date_obj.format(frappe.defaultDatetimeFormat); }, convert_to_system_tz: function(date, format) { @@ -113,11 +114,11 @@ $.extend(frappe.datetime, { return moment().endOf("quarter").format(); }, - year_start: function(){ + year_start: function() { return moment().startOf("year").format(); }, - year_end: function(){ + year_end: function() { return moment().endOf("year").format(); }, @@ -135,22 +136,23 @@ $.extend(frappe.datetime, { str_to_user: function(val, only_time = false) { if (!val) return ""; + const user_date_fmt = frappe.datetime.get_user_date_fmt().toUpperCase(); const user_time_fmt = frappe.datetime.get_user_time_fmt(); - let date_obj = moment(val); let user_format = user_time_fmt; if (only_time) { - date_obj = moment(val, frappe.defaultTimeFormat); + let date_obj = moment(val, frappe.defaultTimeFormat); + return date_obj.format(user_format); } else { - let user_date_fmt = frappe.datetime.get_user_date_fmt().toUpperCase(); - if (typeof val !== "string" || val.indexOf(" ")===-1) { - date_obj = moment(val); + let date_obj = moment(val); + if (typeof val !== "string" || val.indexOf(" ") === -1) { + user_format = user_date_fmt; } else { date_obj = moment(val, "YYYY-MM-DD HH:mm:ss"); user_format = user_date_fmt + " " + user_time_fmt; } + return date_obj.tz(frappe.boot.time_zone.user).format(user_format); } - return date_obj.tz(frappe.boot.time_zone.user).format(user_format); }, get_datetime_as_string: function(d) { @@ -217,9 +219,9 @@ $.extend(frappe.datetime, { return as_obj ? frappe.datetime.moment_to_date_obj(date) : date.format(format); }, - moment_to_date_obj: function(moment) { + moment_to_date_obj: function(moment_obj) { const date_obj = new Date(); - const date_array = moment.toArray(); + const date_array = moment_obj.toArray(); date_obj.setFullYear(date_array[0]); date_obj.setMonth(date_array[1]); date_obj.setDate(date_array[2]); From 515dca29fda58c4979989d0590f59d30c6a6c595 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 1 Dec 2021 12:57:15 +0530 Subject: [PATCH 09/20] fix: Remove unnecessary code - frappe.datetime.str_to_user(value, false) takes care of tz conversion --- frappe/public/js/frappe/form/controls/datetime.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index 3142f1bf0f..d8c67ce2e9 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -40,9 +40,6 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co format_for_input(value) { if (!value) return ""; - if (!frappe.datetime.is_system_time_zone()) { - value = frappe.datetime.convert_to_user_tz(value, true); - } return frappe.datetime.str_to_user(value, false); } From e2bf192633624669693d39a09bc9ac4ccb0cc940 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 1 Dec 2021 12:59:32 +0530 Subject: [PATCH 10/20] fix: Select date of datepicker while doing set_formatted_input - To avoid empty value when clicking datetime for first time --- frappe/public/js/frappe/form/controls/datetime.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index d8c67ce2e9..c7efc3e2c3 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -10,6 +10,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co } this.$input && this.$input.val(this.format_for_input(value)); + this.datepicker.selectDate(frappe.datetime.str_to_obj(value)); } set_date_options() { super.set_date_options(); From 62073d65a0aa948902c6f2f556a3f4971e7e72d7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 1 Dec 2021 13:10:49 +0530 Subject: [PATCH 11/20] fix: Set now date based on system timezone - because set_formatted_value will convert value to user timezone down the line --- frappe/public/js/frappe/form/controls/date.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/date.js b/frappe/public/js/frappe/form/controls/date.js index 9ad81c7e46..ba94531b0f 100644 --- a/frappe/public/js/frappe/form/controls/date.js +++ b/frappe/public/js/frappe/form/controls/date.js @@ -53,7 +53,7 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat let date_format = sysdefaults && sysdefaults.date_format ? sysdefaults.date_format : 'yyyy-mm-dd'; - let now_date = new Date(); + let now_date = new Date(this.get_now_date()); this.today_text = __("Today"); this.date_format = frappe.defaultDateFormat; @@ -112,7 +112,7 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat this.datepicker.update('position', position); } get_now_date() { - return frappe.datetime.now_date(true); + return frappe.datetime.convert_to_system_tz(frappe.datetime.now_date(true)); } set_t_for_today() { var me = this; From 2ca687dac8e591afe2021c1e877819d53a2c31cb Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 3 Dec 2021 13:26:13 +0530 Subject: [PATCH 12/20] fix: Fix timezone conversions - User to System & vice-versa - Fixed infinite loop of setting datepicker value --- frappe/public/js/frappe/form/controls/date.js | 9 ++++++--- frappe/public/js/frappe/form/controls/datetime.js | 9 +++++++-- frappe/public/js/frappe/utils/datetime.js | 13 +++++++------ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/date.js b/frappe/public/js/frappe/form/controls/date.js index ba94531b0f..b964740ace 100644 --- a/frappe/public/js/frappe/form/controls/date.js +++ b/frappe/public/js/frappe/form/controls/date.js @@ -53,8 +53,6 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat let date_format = sysdefaults && sysdefaults.date_format ? sysdefaults.date_format : 'yyyy-mm-dd'; - let now_date = new Date(this.get_now_date()); - this.today_text = __("Today"); this.date_format = frappe.defaultDateFormat; this.datepicker_options = { @@ -62,7 +60,7 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat autoClose: true, todayButton: true, dateFormat: date_format, - startDate: now_date, + startDate: this.get_start_date(), keyboardNav: false, onSelect: () => { this.$input.trigger('change'); @@ -76,6 +74,11 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat } }; } + + get_start_date() { + return new Date(this.get_now_date()); + } + set_datepicker() { this.$input.datepicker(this.datepicker_options); this.datepicker = this.$input.data('datepicker'); diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index c7efc3e2c3..5d0ecb9fe7 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -8,9 +8,14 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co } else if (value === "Today") { value = this.get_now_date(); } + value = this.format_for_input(value); + this.$input && this.$input.val(value); + this.datepicker.selectDate(frappe.datetime.user_to_obj(value)); + } - this.$input && this.$input.val(this.format_for_input(value)); - this.datepicker.selectDate(frappe.datetime.str_to_obj(value)); + get_start_date() { + let value = frappe.datetime.convert_to_user_tz(this.value); + return frappe.datetime.str_to_obj(value); } set_date_options() { super.set_date_options(); diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js index 2fcfd75c5e..7bb6076b72 100644 --- a/frappe/public/js/frappe/utils/datetime.js +++ b/frappe/public/js/frappe/utils/datetime.js @@ -17,8 +17,7 @@ $.extend(frappe.datetime, { // system time zone and then convert the string to user time zone(from User doctype). let date_obj = null; if (frappe.boot.time_zone && frappe.boot.time_zone.system && frappe.boot.time_zone.user) { - date_obj = moment(date) - .tz(frappe.boot.time_zone.system) + date_obj = moment.tz(date, frappe.boot.time_zone.system) .clone() .tz(frappe.boot.time_zone.user); } else { @@ -144,14 +143,16 @@ $.extend(frappe.datetime, { let date_obj = moment(val, frappe.defaultTimeFormat); return date_obj.format(user_format); } else { - let date_obj = moment(val); + let date_obj = moment.tz(val, frappe.boot.time_zone.system); if (typeof val !== "string" || val.indexOf(" ") === -1) { user_format = user_date_fmt; } else { - date_obj = moment(val, "YYYY-MM-DD HH:mm:ss"); user_format = user_date_fmt + " " + user_time_fmt; } - return date_obj.tz(frappe.boot.time_zone.user).format(user_format); + return date_obj + .clone() + .tz(frappe.boot.time_zone.user) + .format(user_format); } }, @@ -207,7 +208,7 @@ $.extend(frappe.datetime, { _date: function(format, as_obj = false) { /** - * Whenever we are getting now_date/datetime, always make sure dates are fetched using usertime zone. + * Whenever we are getting now_date/datetime, always make sure dates are fetched using user time zone. * This is to make sure that time is as per user time zone set in User doctype, If a user had to change the timezone, * we will end up having multiple timezone by not honouring timezone in User doctype. * This will make sure that at any point we know which timezone the user if following and not have random timezone From 9421736fb9604ae2089716da095cdd4c1672df35 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 8 Dec 2021 12:20:22 +0000 Subject: [PATCH 13/20] feat: add number format for BTC --- frappe/utils/data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index d39d32d8df..de0242df07 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -882,7 +882,8 @@ number_format_info = { "#,##,###.##": (".", ",", 2), "#,###.###": (".", ",", 3), "#.###": ("", ".", 0), - "#,###": ("", ",", 0) + "#,###": ("", ",", 0), + "#.########": (".", "", 8) } def get_number_format_info(format): From afb3dbc59cc570f9fc26860d8cfc76ae6f2b1929 Mon Sep 17 00:00:00 2001 From: phot0n Date: Mon, 13 Dec 2021 13:32:51 +0530 Subject: [PATCH 14/20] fix: upload jpeg image with alpha channel --- frappe/utils/image.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/utils/image.py b/frappe/utils/image.py index fcde948f6c..3b0daf565c 100644 --- a/frappe/utils/image.py +++ b/frappe/utils/image.py @@ -30,6 +30,9 @@ def strip_exif_data(content, content_type): original_image = Image.open(io.BytesIO(content)) output = io.BytesIO() + # ref: https://stackoverflow.com/a/48248432 + if content_type == "image/jpeg" and original_image.mode in ("RGBA", "P"): + original_image = original_image.convert("RGB") new_image = Image.new(original_image.mode, original_image.size) new_image.putdata(list(original_image.getdata())) From f6c38b6b6e30d13a26b636e6c71b7a040ba12cf9 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 16 Dec 2021 11:56:55 +0530 Subject: [PATCH 15/20] fix(ux): Show better error message when server script fails --- frappe/core/doctype/server_script/server_script_utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/server_script/server_script_utils.py b/frappe/core/doctype/server_script/server_script_utils.py index 12a8fa47fa..08fbe471d5 100644 --- a/frappe/core/doctype/server_script/server_script_utils.py +++ b/frappe/core/doctype/server_script/server_script_utils.py @@ -41,7 +41,13 @@ def run_server_script_for_doc_event(doc, event): if scripts: # run all scripts for this doctype + event for script_name in scripts: - frappe.get_doc('Server Script', script_name).execute_doc(doc) + try: + frappe.get_doc('Server Script', script_name).execute_doc(doc) + except Exception: + message = frappe._('Error executing Server Script {0}. Open Browser Console to see traceback.').format( + frappe.utils.get_link_to_form('Server Script', script_name) + ) + frappe.throw(title=frappe._('Server Script Error'), msg=message) def get_server_script_map(): # fetch cached server script methods From 7c8659388db2e0bfbfb8bdf2251249e11a30686e Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 16 Dec 2021 12:19:08 +0530 Subject: [PATCH 16/20] fix(server-script): add common python builtins --- frappe/utils/safe_exec.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 6f317855a0..ce24d8ea41 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -184,10 +184,39 @@ def get_safe_globals(): # allow iterators and list comprehension out._getiter_ = iter out._iter_unpack_sequence_ = RestrictedPython.Guards.guarded_iter_unpack_sequence - out.sorted = sorted + + # add common python builtins + out.update(get_python_builtins()) return out +def get_python_builtins(): + return { + 'abs': abs, + 'all': all, + 'any': any, + 'bool': bool, + 'delattr': delattr, + 'dict': dict, + 'enumerate': enumerate, + 'filter': filter, + 'getattr': getattr, + 'hasattr': hasattr, + 'isinstance': isinstance, + 'issubclass': issubclass, + 'list': list, + 'map': map, + 'max': max, + 'min': min, + 'range': range, + 'set': set, + 'setattr': setattr, + 'sorted': sorted, + 'sum': sum, + 'tuple': tuple, + 'type': type, + } + def get_hooks(hook=None, default=None, app_name=None): hooks = frappe.get_hooks(hook=hook, default=default, app_name=app_name) return copy.deepcopy(hooks) From eaec262b25e2bc076a837297f970b3582411b8b1 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 16 Dec 2021 13:35:45 +0530 Subject: [PATCH 17/20] fix: Throw actual exception instead of ValidationError - to make tests pass --- .../core/doctype/server_script/server_script_utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/server_script/server_script_utils.py b/frappe/core/doctype/server_script/server_script_utils.py index 08fbe471d5..b4cfdf0a17 100644 --- a/frappe/core/doctype/server_script/server_script_utils.py +++ b/frappe/core/doctype/server_script/server_script_utils.py @@ -43,11 +43,17 @@ def run_server_script_for_doc_event(doc, event): for script_name in scripts: try: frappe.get_doc('Server Script', script_name).execute_doc(doc) - except Exception: + except Exception as e: message = frappe._('Error executing Server Script {0}. Open Browser Console to see traceback.').format( frappe.utils.get_link_to_form('Server Script', script_name) ) - frappe.throw(title=frappe._('Server Script Error'), msg=message) + exception = type(e) + if getattr(frappe, 'request', None): + # all exceptions throw 500 which is internal server error + # however server script error is a user error + # so we should throw 417 which is expectation failed + exception.http_status_code = 417 + frappe.throw(title=frappe._('Server Script Error'), msg=message, exc=exception) def get_server_script_map(): # fetch cached server script methods From 0bc3ff794b15983a3af41588ef05cd193220e59b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 16 Dec 2021 16:21:05 +0530 Subject: [PATCH 18/20] fix: remove attr helpers and type these can potentially lead to security issues, avoiding for now --- frappe/utils/safe_exec.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index ce24d8ea41..83af3f6b46 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -196,12 +196,9 @@ def get_python_builtins(): 'all': all, 'any': any, 'bool': bool, - 'delattr': delattr, 'dict': dict, 'enumerate': enumerate, 'filter': filter, - 'getattr': getattr, - 'hasattr': hasattr, 'isinstance': isinstance, 'issubclass': issubclass, 'list': list, @@ -210,11 +207,9 @@ def get_python_builtins(): 'min': min, 'range': range, 'set': set, - 'setattr': setattr, 'sorted': sorted, 'sum': sum, 'tuple': tuple, - 'type': type, } def get_hooks(hook=None, default=None, app_name=None): From 2cfebf2a9663f3e8a1478152aad0395a4a80c901 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 16 Dec 2021 20:20:19 +0530 Subject: [PATCH 19/20] fix: set empty lists for unset meta table fields --- frappe/model/meta.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 579191efbe..252c463d3d 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -120,6 +120,11 @@ class Meta(Document): or (not no_nulls and value is None)): out[key] = value + # set empty lists for unset table fields + for table_field in DOCTYPE_TABLE_FIELDS: + if not out.get(table_field.fieldname): + out[table_field.fieldname] = [] + return out return serialize(self) From c6f99207959b3bc48cd48bd9d6e5ace098da5ca1 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 16 Dec 2021 20:31:50 +0530 Subject: [PATCH 20/20] fix: removing map filter these can potentially lead to security issues, avoiding for now --- frappe/utils/safe_exec.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 83af3f6b46..f51efefc85 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -198,11 +198,9 @@ def get_python_builtins(): 'bool': bool, 'dict': dict, 'enumerate': enumerate, - 'filter': filter, 'isinstance': isinstance, 'issubclass': issubclass, 'list': list, - 'map': map, 'max': max, 'min': min, 'range': range,