Skip to content

Commit

Permalink
[FIX] web_editor, website: handle date edition in all lang and format
Browse files Browse the repository at this point in the history
Before d00c0e3, `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 odoo#44484 (comment)

Since d00c0e3, 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 odoo#44484
Closes odoo#45555
Fixes odoo#44047

closes odoo#45725

closes odoo#45929

X-original-commit: 251b880
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>
  • Loading branch information
rdeodoo and JKE-be committed Feb 21, 2020
1 parent 000b214 commit ac1e8f1
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 8 deletions.
43 changes: 37 additions & 6 deletions addons/web_editor/models/ir_qweb.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""

import ast
import babel
import base64
import io
import itertools
Expand All @@ -21,14 +22,15 @@

import pytz
import requests
from datetime import datetime
from lxml import etree, html
from PIL import Image as I
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

Expand Down Expand Up @@ -202,6 +204,21 @@ def attributes(self, record, field_name, options, values):
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
Expand All @@ -210,7 +227,9 @@ def from_html(self, model, field, element):
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):
Expand All @@ -221,15 +240,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
Expand All @@ -239,11 +270,11 @@ def from_html(self, model, field, element):
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)
Expand Down
17 changes: 16 additions & 1 deletion addons/web_editor/static/src/js/editor/rte.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,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
Expand Down Expand Up @@ -534,6 +534,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);
Expand Down Expand Up @@ -648,6 +659,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;
}
Expand Down Expand Up @@ -697,6 +711,7 @@ var RTEWidget = Widget.extend({
clearTimeout(lastTimerId);
}
}

if ($editable.length && (!this.$last || this.$last[0] !== $editable[0])) {
$editable.summernote(this._getConfig($editable));

Expand Down
3 changes: 2 additions & 1 deletion addons/website/static/src/scss/website.edit_mode.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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"]):hover {
&:not([data-oe-model="ir.ui.view"]):not([data-oe-type="html"]):hover,
&.o_editable_date_field_linked {
box-shadow: $o-brand-odoo 0 0 5px 2px inset;
}
}
Expand Down

0 comments on commit ac1e8f1

Please sign in to comment.