Merge branch 'develop' into fix-frappe-qb-set-syntax-in-email-account

This commit is contained in:
Suraj Shetty 2021-12-17 14:03:32 +05:30 committed by GitHub
commit 7c6b3dbc80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 207 additions and 72 deletions

View file

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

View file

@ -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": get_time_zone(),
"user": bootinfo.get("user_info", {}).get(frappe.session.user, {}).get("time_zone", None) or get_time_zone()
}

View file

@ -41,7 +41,19 @@ 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 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)
)
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

View file

@ -32,5 +32,11 @@ frappe.ui.form.on("System Settings", {
frm.set_value('prepared_report_expiry_period', 7);
}
}
},
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();
}
}
});

View file

@ -97,6 +97,7 @@
"fieldname": "time_zone",
"fieldtype": "Select",
"label": "Time Zone",
"read_only": 1,
"reqd": 1
},
{

View file

@ -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();
@ -267,6 +272,12 @@ frappe.ui.form.on('User', {
}
}
});
},
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();
}
}
});

View file

@ -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
@ -74,6 +74,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
@ -227,11 +228,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:
@ -596,6 +597,10 @@ class User(Document):
return user
def set_time_zone(self):
if not self.time_zone:
self.time_zone = get_time_zone()
@frappe.whitelist()
def get_timezones():
import pytz

View file

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

View file

@ -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.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');
@ -77,6 +75,11 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat
...(this.get_df_options())
};
}
get_start_date() {
return new Date(this.get_now_date());
}
set_datepicker() {
this.$input.datepicker(this.datepicker_options);
this.datepicker = this.$input.data('datepicker');
@ -113,7 +116,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;

View file

@ -1,4 +1,22 @@
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();
}
value = this.format_for_input(value);
this.$input && this.$input.val(value);
this.datepicker.selectDate(frappe.datetime.user_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();
this.today_text = __("Now");
@ -14,10 +32,31 @@ 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_system_time_zone()) {
value = frappe.datetime.convert_to_system_tz(value, true);
}
return value;
}
}
format_for_input(value) {
if (!value) return "";
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 = this.get_user_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)) {
@ -26,6 +65,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 : frappe.sys_defaults.time_zone;
}
set_datepicker() {
super.set_datepicker();
if (this.datepicker.opts.timeFormat.indexOf('s') == -1) {

View file

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

View file

@ -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 || 'HH:mm:ss'));
return moment(frappe.datetime.convert_to_user_tz(value))
.format(frappe.boot.sysdefaults.date_format.toUpperCase() + ' ' + frappe.boot.sysdefaults.time_format || 'HH:mm:ss');
} else {
return "";
}

View file

@ -13,33 +13,48 @@ 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 && frappe.boot.time_zone.user) {
date_obj = moment.tz(date, frappe.boot.time_zone.system)
.clone()
.tz(frappe.boot.time_zone.user);
} 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 && frappe.boot.time_zone.user) {
date_obj = moment.tz(date, frappe.boot.time_zone.user)
.clone()
.tz(frappe.boot.time_zone.system);
} 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_system_time_zone: function() {
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() {
if(frappe.sys_defaults.time_zone) {
return moment().tz(frappe.sys_defaults.time_zone).utcOffset() === moment().utcOffset();
} else {
return true;
}
return frappe.datetime.is_system_time_zone();
},
str_to_obj: function(d) {
@ -98,11 +113,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();
},
@ -119,19 +134,25 @@ $.extend(frappe.datetime, {
},
str_to_user: function(val, only_time = false) {
if(!val) return "";
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 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) {
let date_obj = moment(val, frappe.defaultTimeFormat);
return date_obj.format(user_format);
} else {
return moment(val, "YYYY-MM-DD HH:mm:ss").format(user_date_fmt + " " + user_time_fmt);
let date_obj = moment.tz(val, frappe.boot.time_zone.system);
if (typeof val !== "string" || val.indexOf(" ") === -1) {
user_format = user_date_fmt;
} else {
user_format = user_date_fmt + " " + user_time_fmt;
}
return date_obj
.clone()
.tz(frappe.boot.time_zone.user)
.format(user_format);
}
},
@ -186,23 +207,22 @@ $.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 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
* when the timezone of the local machine changes.
*/
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);
},
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]);

View file

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

View file

@ -882,7 +882,8 @@ number_format_info = {
"#,##,###.##": (".", ",", 2),
"#,###.###": (".", ",", 3),
"#.###": ("", ".", 0),
"#,###": ("", ",", 0)
"#,###": ("", ",", 0),
"#.########": (".", "", 8)
}
def get_number_format_info(format):

View file

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

View file

@ -184,10 +184,32 @@ 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,
'dict': dict,
'enumerate': enumerate,
'isinstance': isinstance,
'issubclass': issubclass,
'list': list,
'max': max,
'min': min,
'range': range,
'set': set,
'sorted': sorted,
'sum': sum,
'tuple': tuple,
}
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)