[FIX] web_editor, website: handle date edition in all lang and format
Before d00c0e317, `Datetime` fields would be editable in frontend but would have unexpected results, especially in non-English languages, for when the english lang format had been changed. It would also crash when saving non-English strings, such as `Lundi`. For more details, see https://github.com/odoo/odoo/pull/44484#issuecomment-586850490 Since d00c0e317, only date displayed in lang format would be editable, which case is Event page in Odoo 12.0. Everywhere else, the format is changed for a nicer layout, either with `widget=XXX` or `t-options=YYY`, such as: `<time t-field="record.date" t-options='{"format": "MMM d, yyyy"}'/>` `<time t-field="record.date" t-options="{'time_only': 'true', 'format': 'short'}"/>` When a date parsing crashes during editor save, the problem is not only that the date can be saved, but the whole changes of the page are lost, as they won't be saved either. This commit attempts to fix every languages cases, regardless of the website lang or user lang. To do so, we store the date in the user lang format in a data attribute of every date field in the DOM. Once the field is clicked (to edit probably), that value will replace the one displayed according to the widget/options. That way, dates will always be sent to the server in the user lang format, avoiding any possible mismatch. This whole fix apply to `Datetime` and `Date` fields. opw-2183055 Closes #44484 Closes #45555 Fixes #44047 closes odoo/odoo#45725 closes odoo/odoo#45997 X-original-commit: c0db9bb8027ff926c619aebe6e0a5cdd05808232 Original-signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com> Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com> Co-authored-by: Romain Derie <rde@odoo.com> Co-authored-by: Jeremy Kersten <jke@odoo.com>
This commit is contained in:
co-authored by
Jeremy Kersten
parent
4a6d3ae934
commit
4eafd78847
@@ -9,6 +9,7 @@ Also, adds methods to convert values back to Odoo models.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import babel
|
||||
import base64
|
||||
import io
|
||||
import itertools
|
||||
@@ -21,6 +22,7 @@ from datetime import datetime
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from lxml import etree, html
|
||||
from PIL import Image as I
|
||||
from werkzeug import urls
|
||||
@@ -28,7 +30,7 @@ from werkzeug import urls
|
||||
import odoo.modules
|
||||
|
||||
from odoo import api, models, fields
|
||||
from odoo.tools import ustr, pycompat
|
||||
from odoo.tools import ustr, posix_to_ldml, pycompat
|
||||
from odoo.tools import html_escape as escape
|
||||
from odoo.addons.base.models import ir_qweb
|
||||
|
||||
@@ -203,6 +205,21 @@ class Date(models.AbstractModel):
|
||||
attrs = super(Date, self).attributes(record, field_name, options, values)
|
||||
if options.get('inherit_branding'):
|
||||
attrs['data-oe-original'] = record[field_name]
|
||||
|
||||
if record._fields[field_name].type == 'datetime':
|
||||
attrs = self.env['ir.qweb.field.datetime'].attributes(record, field_name, options, values)
|
||||
attrs['data-oe-type'] = 'datetime'
|
||||
return attrs
|
||||
|
||||
lg = self.env['res.lang']._lang_get(self.env.user.lang)
|
||||
locale = babel.Locale.parse(lg.code)
|
||||
babel_format = value_format = posix_to_ldml(lg.date_format, locale=locale)
|
||||
|
||||
if record[field_name]:
|
||||
date = fields.Date.from_string(record[field_name])
|
||||
value_format = pycompat.to_text(babel.dates.format_date(date, format=babel_format, locale=locale))
|
||||
|
||||
attrs['data-oe-original-with-format'] = value_format
|
||||
return attrs
|
||||
|
||||
@api.model
|
||||
@@ -211,7 +228,9 @@ class Date(models.AbstractModel):
|
||||
if not value:
|
||||
return False
|
||||
|
||||
return value
|
||||
lg = self.env['res.lang']._lang_get(self.env.user.lang)
|
||||
date = datetime.strptime(value, lg.date_format)
|
||||
return fields.Date.to_string(date)
|
||||
|
||||
|
||||
class DateTime(models.AbstractModel):
|
||||
@@ -222,15 +241,27 @@ class DateTime(models.AbstractModel):
|
||||
@api.model
|
||||
def attributes(self, record, field_name, options, values):
|
||||
attrs = super(DateTime, self).attributes(record, field_name, options, values)
|
||||
|
||||
if options.get('inherit_branding'):
|
||||
value = record[field_name]
|
||||
|
||||
lg = self.env['res.lang']._lang_get(self.env.user.lang)
|
||||
locale = babel.Locale.parse(lg.code)
|
||||
babel_format = value_format = posix_to_ldml('%s %s' % (lg.date_format, lg.time_format), locale=locale)
|
||||
tz = record.env.context.get('tz') or self.env.user.tz
|
||||
|
||||
if isinstance(value, str):
|
||||
value = fields.Datetime.from_string(value)
|
||||
|
||||
if value:
|
||||
# convert from UTC (server timezone) to user timezone
|
||||
value = fields.Datetime.context_timestamp(self, timestamp=value)
|
||||
value = fields.Datetime.context_timestamp(self.with_context(tz=tz), timestamp=value)
|
||||
value_format = pycompat.to_text(babel.dates.format_datetime(value, format=babel_format, locale=locale))
|
||||
value = fields.Datetime.to_string(value)
|
||||
|
||||
attrs['data-oe-original'] = value
|
||||
attrs['data-oe-original-with-format'] = value_format
|
||||
attrs['data-oe-original-tz'] = tz
|
||||
return attrs
|
||||
|
||||
@api.model
|
||||
@@ -240,11 +271,11 @@ class DateTime(models.AbstractModel):
|
||||
return False
|
||||
|
||||
# parse from string to datetime
|
||||
date_format = self.env['res.lang']._lang_get(self.env.user.lang).date_format + ' %H:%M'
|
||||
dt = datetime.strptime(value, date_format)
|
||||
lg = self.env['res.lang']._lang_get(self.env.user.lang)
|
||||
dt = datetime.strptime(value, '%s %s' % (lg.date_format, lg.time_format))
|
||||
|
||||
# convert back from user's timezone to UTC
|
||||
tz_name = self.env.context.get('tz') or self.env.user.tz
|
||||
tz_name = element.attrib.get('data-oe-original-tz') or self.env.context.get('tz') or self.env.user.tz
|
||||
if tz_name:
|
||||
try:
|
||||
user_tz = pytz.timezone(tz_name)
|
||||
|
||||
@@ -482,7 +482,7 @@ var RTEWidget = Widget.extend({
|
||||
|
||||
$('.o_editable')
|
||||
.destroy()
|
||||
.removeClass('o_editable o_is_inline_editable');
|
||||
.removeClass('o_editable o_is_inline_editable o_editable_date_field_linked o_editable_date_field_format_changed');
|
||||
|
||||
var $dirty = $('.o_dirty');
|
||||
$dirty
|
||||
@@ -550,6 +550,17 @@ var RTEWidget = Widget.extend({
|
||||
* @param {jQuery} $editable
|
||||
*/
|
||||
_enableEditableArea: function ($editable) {
|
||||
if ($editable.data('oe-type') === "datetime" || $editable.data('oe-type') === "date") {
|
||||
var selector = '[data-oe-id="' + $editable.data('oe-id') + '"]';
|
||||
selector += '[data-oe-field="' + $editable.data('oe-field') + '"]';
|
||||
selector += '[data-oe-model="' + $editable.data('oe-model') + '"]';
|
||||
var $linkedFieldNodes = this.editable().find(selector).addBack(selector);
|
||||
$linkedFieldNodes.not($editable).addClass('o_editable_date_field_linked');
|
||||
if (!$editable.hasClass('o_editable_date_field_format_changed')) {
|
||||
$linkedFieldNodes.html($editable.data('oe-original-with-format'));
|
||||
$linkedFieldNodes.addClass('o_editable_date_field_format_changed');
|
||||
}
|
||||
}
|
||||
if ($editable.data('oe-type') === "monetary") {
|
||||
$editable.attr('contenteditable', false);
|
||||
$editable.find('.oe_currency_value').attr('contenteditable', true);
|
||||
@@ -664,6 +675,9 @@ var RTEWidget = Widget.extend({
|
||||
var $target = $(ev.target);
|
||||
var $editable = $target.closest('.o_editable');
|
||||
|
||||
if (this && this.$last && this.$last.length && this.$last[0] !== $target[0]) {
|
||||
$('.o_editable_date_field_linked').removeClass('o_editable_date_field_linked');
|
||||
}
|
||||
if (!$editable.length || $.summernote.core.dom.isContentEditableFalse($target)) {
|
||||
return;
|
||||
}
|
||||
@@ -716,6 +730,7 @@ var RTEWidget = Widget.extend({
|
||||
clearTimeout(lastTimerId);
|
||||
}
|
||||
}
|
||||
|
||||
if ($editable.length && (!this.$last || this.$last[0] !== $editable[0])) {
|
||||
$editable.summernote(this._getConfig($editable));
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ $-editor-messages-margin-x: 2%;
|
||||
|
||||
.o_editable {
|
||||
&:not(:empty), &[data-oe-type] {
|
||||
&:not([data-oe-model="ir.ui.view"]):not([data-oe-type="html"]):not(.o_editable_no_shadow):hover {
|
||||
&:not([data-oe-model="ir.ui.view"]):not([data-oe-type="html"]):not(.o_editable_no_shadow):hover,
|
||||
&.o_editable_date_field_linked {
|
||||
box-shadow: $o-brand-odoo 0 0 5px 2px inset;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user