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

Signed-off-by: Romain Derie <rdeodoo@users.noreply.github.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 19, 2020
1 parent 58a1b6f commit 251b880
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 8 deletions.
42 changes: 36 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 @@ -20,15 +21,15 @@

import pytz
import requests
from dateutil import parser
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.ir import ir_qweb

Expand Down Expand Up @@ -191,6 +192,21 @@ class Date(models.AbstractModel):
def attributes(self, record, field_name, options, values):
attrs = super(Date, self).attributes(record, field_name, options, values)
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 @@ -199,7 +215,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 @@ -210,13 +228,24 @@ class DateTime(models.AbstractModel):
def attributes(self, record, field_name, options, values):
attrs = super(DateTime, self).attributes(record, field_name, options, values)
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, pycompat.string_types):
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 @@ -226,10 +255,11 @@ def from_html(self, model, field, element):
return False

# parse from string to datetime
dt = parser.parse(value)
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 @@ -439,7 +439,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 @@ -507,6 +507,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 @@ -613,6 +624,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 @@ -656,6 +670,7 @@ var RTEWidget = Widget.extend({
}, 150); // setTimeout to remove flickering when change to editable zone (re-create an editor)
this.$last = null;
}

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/less/website.edit_mode.less
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

.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: @odoo-brand-primary 0 0 5px 2px inset;
}
}
Expand Down

0 comments on commit 251b880

Please sign in to comment.