+ % if not is_html_empty(object.event_id.description):
+
Description of the event
+ ${object.event_id.description | safe}
+ % endif
diff --git a/addons/calendar/models/calendar_event.py b/addons/calendar/models/calendar_event.py
index c8ee1a71b04..6ae4f68e7f8 100644
--- a/addons/calendar/models/calendar_event.py
+++ b/addons/calendar/models/calendar_event.py
@@ -9,13 +9,12 @@ import pytz
from werkzeug.urls import url_join
from odoo import api, fields, models
-from odoo import tools
from odoo.addons.base.models.res_partner import _tz_get
from odoo.addons.calendar.models.calendar_attendee import Attendee
from odoo.addons.calendar.models.calendar_recurrence import weekday_to_field, RRULE_TYPE_SELECTION, END_TYPE_SELECTION, MONTH_BY_SELECTION, WEEKDAY_SELECTION, BYDAY_SELECTION
from odoo.tools.translate import _
from odoo.tools.misc import get_lang
-from odoo.tools import pycompat, html_escape
+from odoo.tools import pycompat, html2plaintext, is_html_empty
from odoo.exceptions import UserError, ValidationError, AccessError
_logger = logging.getLogger(__name__)
@@ -96,7 +95,7 @@ class Meeting(models.Model):
# description
name = fields.Char('Meeting Subject', required=True)
- description = fields.Text('Description')
+ description = fields.Html('Description')
user_id = fields.Many2one('res.users', 'Organizer', default=lambda self: self.env.user)
partner_id = fields.Many2one(
'res.partner', string='Scheduled by', related='user_id.partner_id', readonly=True)
@@ -627,7 +626,7 @@ class Meeting(models.Model):
if 'name' in fields:
activity_values['summary'] = event.name
if 'description' in fields:
- activity_values['note'] = tools.plaintext2html(event.description)
+ activity_values['note'] = event.description
if 'start' in fields:
# self.start is a datetime UTC *only when the event is not allday*
# activty.date_deadline is a date (No TZ, but should represent the day in which the user's TZ is)
@@ -837,8 +836,12 @@ class Meeting(models.Model):
event.add('dtstart').value = ics_datetime(meeting.start, meeting.allday)
event.add('dtend').value = ics_datetime(meeting.stop, meeting.allday)
event.add('summary').value = meeting.name
- if meeting.description:
- event.add('description').value = meeting.description
+ if not is_html_empty(meeting.description):
+ if 'appointment_type_id' in meeting._fields and self.appointment_type_id:
+ # convert_online_event_desc_to_text method for correct data formatting in external calendars
+ event.add('description').value = self.convert_online_event_desc_to_text(meeting.description)
+ else:
+ event.add('description').value = html2plaintext(meeting.description)
if meeting.location:
event.add('location').value = meeting.location
if meeting.rrule:
@@ -866,6 +869,23 @@ class Meeting(models.Model):
return result
+ def convert_online_event_desc_to_text(self, description):
+ """
+ We can sync the calendar events with google calendar, iCal and Outlook, and we
+ also pass the event description along with other data. This description needs
+ to be in plaintext to be displayed properly in above platforms. Because online
+ events have fixed format for the description, this method removes some specific
+ html tags, and converts it into readable plaintext (to be used in external
+ calendars). Note that for regular (offline) events, we simply use the standard
+ `html2plaintext` method instead.
+ """
+ desc_str = str(description)
+ tags_to_replace = ["
", "
", "
"]
+ for tag in tags_to_replace:
+ desc_str = desc_str.replace(tag, "")
+ desc_str = desc_str.replace("
", " ")
+ return html2plaintext(desc_str)
+
@api.model
def _get_display_time(self, start, stop, zduration, zallday):
""" Return date and time (from to from) based on duration with timezone in string. Eg :
@@ -948,56 +968,3 @@ class Meeting(models.Model):
'id', 'active', 'allday',
'duration', 'user_id', 'interval',
'count', 'rrule', 'recurrence_id', 'show_as', 'privacy'}
-
- def description_to_html_lines(self):
- """ Description could contain some structure content, depending on
- its use. Notably appointment could add some structured data in it
- in addition to free text. Purpose of this method is to generate a
- simple html.
-
- Input (self.description) example:
-Some free text salespeople added
-as multi line
- * Mobile: +320475000000
- * Email: my.email@test.example.com
- * SingleLine: answer
- * MultiLine:
-Answer1
-Answer2
-Answer3
- * Dropdown: answer
- * Radio: answer
- * Checkboxes: answer1, answer2
-Some free text salespeople added
-as multi line
-
- Output: a list of items[
-'Some free text salespeople added as multi line',
-'Mobile: +320475000000',
-'Email: my.email@test.example.com',
-'SingleLine: answer',
-'MultiLine: Answer1 Answer2 Answer3',
-'Dropdown: answer',
-'Radio: answer',
-'Checkboxes: answer1, answer2',
-'Some free text salespeople added as multi line']
-
- Each item of returned list is escaped so that only our intended
- are html tags. It should therefore be safe.
- """
-
- final_lines = []
- parsed_lines = []
- for line in self.description.split('\n'):
- if not line.strip():
- continue
- # new line
- if line.startswith(' *'):
- if parsed_lines:
- final_lines.append(' '.join(html_escape(line) for line in parsed_lines))
- parsed_lines = [line.lstrip(' *')]
- else:
- parsed_lines.append(line)
- if parsed_lines:
- final_lines.append(' '.join(html_escape(line) for line in parsed_lines))
- return final_lines
diff --git a/addons/calendar/models/mail_activity.py b/addons/calendar/models/mail_activity.py
index 58ca5ff75d4..7f483ca27af 100644
--- a/addons/calendar/models/mail_activity.py
+++ b/addons/calendar/models/mail_activity.py
@@ -34,7 +34,10 @@ class MailActivity(models.Model):
if feedback:
for event in events:
description = event.description
- description = '%s\n%s%s' % (description or '', _("Feedback: "), feedback)
+ description = '%s %s' % (
+ description if not tools.is_html_empty(description) else '',
+ _('Feedback: %(feedback)s', tools.plaintext2html(feedback)) if feedback else '',
+ )
event.write({'description': description})
return messages, activities
diff --git a/addons/calendar/tests/test_calendar.py b/addons/calendar/tests/test_calendar.py
index ebece3f11de..4083877516c 100644
--- a/addons/calendar/tests/test_calendar.py
+++ b/addons/calendar/tests/test_calendar.py
@@ -123,7 +123,7 @@ class TestCalendar(SavepointCaseWithUserDemo):
self.assertEqual(test_record.activity_ids.date_deadline, (now + timedelta(days=-2)).date())
# update event with a description that have a special character and a new line
- test_description3 = 'Test & \n Description'
+ test_description3 = 'Test & Description'
test_note3 = '
Test & Description
'
test_event.write({
'description': test_description3,
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index fb44fc24f85..1bea5e37ae0 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -121,7 +121,7 @@ class Lead(models.Model):
'res.company', string='Company', index=True,
compute='_compute_company_id', readonly=False, store=True)
referred = fields.Char('Referred By')
- description = fields.Text('Notes')
+ description = fields.Html('Notes')
active = fields.Boolean('Active', default=True, tracking=True)
type = fields.Selection([
('lead', 'Lead'), ('opportunity', 'Opportunity')],
@@ -1376,7 +1376,7 @@ class Lead(models.Model):
def _merge_get_fields_specific(self):
return {
- 'description': lambda fname, leads: '\n\n'.join(desc for desc in leads.mapped('description') if desc),
+ 'description': lambda fname, leads: '
'.join(desc for desc in leads.mapped('description') if not is_html_empty(desc)),
'type': lambda fname, leads: 'opportunity' if any(lead.type == 'opportunity' for lead in leads) else 'lead',
'priority': lambda fname, leads: max(leads.mapped('priority')) if leads else False,
}
diff --git a/addons/crm/tests/common.py b/addons/crm/tests/common.py
index f863b577c94..daa7baa5fc3 100644
--- a/addons/crm/tests/common.py
+++ b/addons/crm/tests/common.py
@@ -399,7 +399,7 @@ class TestCrmCommon(TestSalesCommon, MailCase):
def _get_description():
values = [_find_value(lead, 'description') for lead in leads]
- return '\n\n'.join(value for value in values if value)
+ return '
'.join(value for value in values if value)
def _get_priority():
values = [_find_value(lead, 'priority') for lead in leads]
diff --git a/addons/crm/tests/test_crm_lead_merge.py b/addons/crm/tests/test_crm_lead_merge.py
index 97206dfec73..8d313dc47fa 100644
--- a/addons/crm/tests/test_crm_lead_merge.py
+++ b/addons/crm/tests/test_crm_lead_merge.py
@@ -88,7 +88,7 @@ class TestLeadMerge(TestLeadMergeCommon):
# and exclude inactive leads, but that's not written anywhere ... intended ??
self.assertEqual(merge.opportunity_ids, self.leads - self.lead_w_partner_company - self.lead_w_email_lost)
ordered_merge = self.lead_w_contact + self.lead_w_email + self.lead_1 + self.lead_w_partner
- ordered_merge_description = '\n\n'.join(l.description for l in ordered_merge)
+ ordered_merge_description = '
'.join(l.description for l in ordered_merge)
# merged opportunity: in this test, all input are leads. Confidence is based on stage
# sequence -> lead_w_contact has a stage sequence of 30
diff --git a/addons/crm_livechat/models/mail_channel.py b/addons/crm_livechat/models/mail_channel.py
index c051309c813..64313998493 100644
--- a/addons/crm_livechat/models/mail_channel.py
+++ b/addons/crm_livechat/models/mail_channel.py
@@ -28,9 +28,9 @@ class MailChannel(models.Model):
:param key: operator input in chat ('/lead Lead about Product')
"""
description = ''.join(
- '%s: %s\n' % (message.author_id.name or self.anonymous_name, message.body)
+ '%s: %s ' % (message.author_id.name or self.anonymous_name, html2plaintext(message.body))
for message in self.message_ids.sorted('id')
- )
+ ) # converting message body back to plaintext for correct data formating in description
# if public user is part of the chat: consider lead to be linked to an
# anonymous user whatever the participants. Otherwise keep only share
# partners (no user or portal user) to link to the lead.
@@ -48,7 +48,7 @@ class MailChannel(models.Model):
'partner_id': customers[0].id if customers else False,
'user_id': False,
'team_id': False,
- 'description': html2plaintext(description),
+ 'description': description,
'referred': partner.name,
'source_id': utm_source and utm_source.id,
})
diff --git a/addons/event/models/event_event.py b/addons/event/models/event_event.py
index 0b1d957f7ec..e14899d4dab 100644
--- a/addons/event/models/event_event.py
+++ b/addons/event/models/event_event.py
@@ -106,7 +106,7 @@ class EventEvent(models.Model):
return self.env['ir.ui.view']._render_template('event.event_default_descripton')
name = fields.Char(string='Event', translate=True, required=True)
- note = fields.Text(string='Note')
+ note = fields.Html(string='Note')
description = fields.Html(string='Description', translate=html_translate, sanitize_attributes=False, sanitize_form=False, default=_default_description)
active = fields.Boolean(default=True)
user_id = fields.Many2one(
diff --git a/addons/event_crm/models/event_lead_rule.py b/addons/event_crm/models/event_lead_rule.py
index 08c5c114cb0..f5c9ec1c7e5 100644
--- a/addons/event_crm/models/event_lead_rule.py
+++ b/addons/event_crm/models/event_lead_rule.py
@@ -177,7 +177,7 @@ class EventLeadRule(models.Model):
additionnal_description = group_registrations._get_lead_description(_("New registrations"), line_counter=True)
for lead in toupdate_leads:
lead.write({
- 'description': "%s\n%s" % (lead.description, additionnal_description),
+ 'description': "%s %s" % (lead.description, additionnal_description),
'registration_ids': [(4, reg.id) for reg in group_registrations],
})
elif group_registrations:
diff --git a/addons/event_crm/models/event_registration.py b/addons/event_crm/models/event_registration.py
index 033bb0d3aec..111cef325b8 100644
--- a/addons/event_crm/models/event_registration.py
+++ b/addons/event_crm/models/event_registration.py
@@ -132,7 +132,7 @@ class EventRegistration(models.Model):
upd_description_fields = [field for field in self._get_lead_description_fields() if field in new_vals.keys()]
if any(new_vals[field] != old_vals[field] for field in upd_description_fields):
for lead in leads_attendee:
- lead_values['description'] = "%s\n%s" % (
+ lead_values['description'] = "%s %s" % (
lead.description,
registration._get_lead_description(_("Updated registrations"), line_counter=True)
)
@@ -148,7 +148,7 @@ class EventRegistration(models.Model):
if not lead.partner_id:
lead_values['description'] = lead.registration_ids._get_lead_description(_("Participants"), line_counter=True)
elif new_vals['partner_id'] != lead.partner_id.id:
- lead_values['description'] = lead.description + "\n" + lead.registration_ids._get_lead_description(_("Updated registrations"), line_counter=True, line_suffix=_("(updated)"))
+ lead_values['description'] = lead.description + " " + lead.registration_ids._get_lead_description(_("Updated registrations"), line_counter=True, line_suffix=_("(updated)"))
if lead_values:
lead.write(lead_values)
@@ -232,17 +232,16 @@ class EventRegistration(models.Model):
"""
reg_lines = [
registration._get_lead_description_registration(
- prefix="%s. " % (index + 1) if line_counter else "",
line_suffix=line_suffix
- ) for index, registration in enumerate(self)
+ ) for registration in self
]
- return ("%s\n" % prefix if prefix else "") + ("\n".join(reg_lines))
+ return ("%s " % prefix if prefix else "") + (
+ "" if line_counter else "
") + ("".join(reg_lines)) + ("
" if line_counter else "")
- def _get_lead_description_registration(self, prefix='', line_suffix=''):
+ def _get_lead_description_registration(self, line_suffix=''):
""" Build the description line specific to a given registration. """
self.ensure_one()
- return "%s%s (%s)%s" % (
- prefix or "",
+ return "
%s (%s)%s
" % (
self.name or self.partner_id.name or self.email,
" - ".join(self[field] for field in ('email', 'phone') if self[field]),
" %s" % line_suffix if line_suffix else "",
diff --git a/addons/fleet/models/fleet_vehicle.py b/addons/fleet/models/fleet_vehicle.py
index 5ac7259de62..0ee1d3005f9 100644
--- a/addons/fleet/models/fleet_vehicle.py
+++ b/addons/fleet/models/fleet_vehicle.py
@@ -18,7 +18,7 @@ class FleetVehicle(models.Model):
return state if state and state.id else False
name = fields.Char(compute="_compute_vehicle_name", store=True)
- description = fields.Text("Vehicle Description")
+ description = fields.Html("Vehicle Description")
active = fields.Boolean('Active', default=True, tracking=True)
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
diff --git a/addons/fleet/models/fleet_vehicle_log_contract.py b/addons/fleet/models/fleet_vehicle_log_contract.py
index 0f9db7c3110..6ac0b181a38 100644
--- a/addons/fleet/models/fleet_vehicle_log_contract.py
+++ b/addons/fleet/models/fleet_vehicle_log_contract.py
@@ -46,7 +46,7 @@ class FleetVehicleLogContract(models.Model):
help='Choose whether the contract is still valid or not',
tracking=True,
copy=False)
- notes = fields.Text('Terms and Conditions', help='Write here all supplementary information relative to this contract', copy=False)
+ notes = fields.Html('Terms and Conditions', help='Write here all supplementary information relative to this contract', copy=False)
cost_generated = fields.Monetary('Recurring Cost')
cost_frequency = fields.Selection([
('no', 'No'),
diff --git a/addons/gamification/data/mail_template_data.xml b/addons/gamification/data/mail_template_data.xml
index 380e18fd470..6a0fe284e85 100644
--- a/addons/gamification/data/mail_template_data.xml
+++ b/addons/gamification/data/mail_template_data.xml
@@ -36,7 +36,7 @@
Congratulations ${object.user_id.name} !
You just received badge ${object.badge_id.name} !
- % if object.badge_id.description
+ % if not is_html_empty(object.badge_id.description)
diff --git a/addons/sale_management/models/sale_order.py b/addons/sale_management/models/sale_order.py
index 165d5acf59b..4bbc71c34de 100644
--- a/addons/sale_management/models/sale_order.py
+++ b/addons/sale_management/models/sale_order.py
@@ -5,6 +5,7 @@ from datetime import timedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
+from odoo.tools import is_html_empty
class SaleOrder(models.Model):
@@ -53,7 +54,7 @@ class SaleOrder(models.Model):
def onchange_partner_id(self):
super(SaleOrder, self).onchange_partner_id()
template = self.sale_order_template_id.with_context(lang=self.partner_id.lang)
- self.note = template.note or self.note
+ self.note = template.note if not is_html_empty(template.note) else self.note
def _compute_line_data_for_template_change(self, line):
return {
@@ -145,7 +146,7 @@ class SaleOrder(models.Model):
self.require_signature = template.require_signature
self.require_payment = template.require_payment
- if template.note:
+ if not is_html_empty(template.note):
self.note = template.note
def action_confirm(self):
diff --git a/addons/sale_management/models/sale_order_template.py b/addons/sale_management/models/sale_order_template.py
index f1cdeb1bb7a..1f303f298d4 100644
--- a/addons/sale_management/models/sale_order_template.py
+++ b/addons/sale_management/models/sale_order_template.py
@@ -17,7 +17,7 @@ class SaleOrderTemplate(models.Model):
name = fields.Char('Quotation Template', required=True)
sale_order_template_line_ids = fields.One2many('sale.order.template.line', 'sale_order_template_id', 'Lines', copy=True)
- note = fields.Text('Terms and conditions', translate=True)
+ note = fields.Html('Terms and conditions', translate=True)
sale_order_template_option_ids = fields.One2many('sale.order.template.option', 'sale_order_template_id', 'Optional Products', copy=True)
number_of_days = fields.Integer('Quotation Duration',
help='Number of days for the validity date computation of the quotation')
diff --git a/addons/sale_quotation_builder/views/sale_portal_templates.xml b/addons/sale_quotation_builder/views/sale_portal_templates.xml
index 7b4d9c36f8b..527ad78318e 100644
--- a/addons/sale_quotation_builder/views/sale_portal_templates.xml
+++ b/addons/sale_quotation_builder/views/sale_portal_templates.xml
@@ -71,7 +71,7 @@
-
+
Terms & Conditions
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index 3fc466b6a38..a7052f0fd18 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -626,8 +626,8 @@ class ProductTemplate(models.Model):
('lot', 'By Lots'),
('none', 'No Tracking')], string="Tracking", help="Ensure the traceability of a storable product in your warehouse.", default='none', required=True)
description_picking = fields.Text('Description on Picking', translate=True)
- description_pickingout = fields.Text('Description on Delivery Orders', translate=True)
- description_pickingin = fields.Text('Description on Receptions', translate=True)
+ description_pickingout = fields.Html('Description on Delivery Orders', translate=True)
+ description_pickingin = fields.Html('Description on Receptions', translate=True)
qty_available = fields.Float(
'Quantity On Hand', compute='_compute_quantities', search='_search_qty_available',
compute_sudo=False, digits='Product Unit of Measure')
diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py
index dadb07e7ffa..5d330d40f32 100644
--- a/addons/stock/models/stock_location.py
+++ b/addons/stock/models/stock_location.py
@@ -51,7 +51,7 @@ class Location(models.Model):
'stock.location', 'Parent Location', index=True, ondelete='cascade', check_company=True,
help="The parent location that includes this location. Example : The 'Dispatch Zone' is the 'Gate 1' parent location.")
child_ids = fields.One2many('stock.location', 'location_id', 'Contains')
- comment = fields.Text('Additional Information')
+ comment = fields.Html('Additional Information')
posx = fields.Integer('Corridor (X)', default=0, help="Optional localization details, for information purpose only")
posy = fields.Integer('Shelves (Y)', default=0, help="Optional localization details, for information purpose only")
posz = fields.Integer('Height (Z)', default=0, help="Optional localization details, for information purpose only")
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index d00d3a06e51..b6b31b39255 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -255,7 +255,7 @@ class Picking(models.Model):
'Source Document', index=True,
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
help="Reference of the document")
- note = fields.Text('Notes')
+ note = fields.Html('Notes')
backorder_id = fields.Many2one(
'stock.picking', 'Back Order of',
copy=False, index=True, readonly=True,
diff --git a/addons/web/models/base_document_layout.py b/addons/web/models/base_document_layout.py
index 8502031278f..7f729990c19 100644
--- a/addons/web/models/base_document_layout.py
+++ b/addons/web/models/base_document_layout.py
@@ -29,8 +29,8 @@ class BaseDocumentLayout(models.TransientModel):
logo = fields.Binary(related='company_id.logo', readonly=False)
preview_logo = fields.Binary(related='logo', string="Preview logo")
- report_header = fields.Text(related='company_id.report_header', readonly=False)
- report_footer = fields.Text(related='company_id.report_footer', readonly=False)
+ report_header = fields.Html(related='company_id.report_header', readonly=False)
+ report_footer = fields.Html(related='company_id.report_footer', readonly=False)
# The paper format changes won't be reflected in the preview.
paperformat_id = fields.Many2one(related='company_id.paperformat_id', readonly=False)
diff --git a/addons/web/static/src/js/libs/jSignatureCustom.js b/addons/web/static/src/js/libs/jSignatureCustom.js
index ca8306af286..da4ab05be15 100644
--- a/addons/web/static/src/js/libs/jSignatureCustom.js
+++ b/addons/web/static/src/js/libs/jSignatureCustom.js
@@ -34,15 +34,11 @@ function _renderImageOnCanvas( data, formattype, rerendercallable ) {
img.src = 'data:' + formattype + ',' + data;
}
+// Override
// Supported image types
-const imageTypes = [
+[
'image',
'image/png;base64',
'image/jpeg;base64',
'image/jpg;base64',
-];
-
-// Override
-for (const imageType of imageTypes) {
- $.fn.jSignature("addPlugin", "import", imageType, _renderImageOnCanvas);
-}
+].forEach(imageType => $.fn.jSignature("addPlugin", "import", imageType, _renderImageOnCanvas));
diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js
index 6929ca6f2a2..a217fc51b30 100644
--- a/addons/web/static/src/js/views/kanban/kanban_record.js
+++ b/addons/web/static/src/js/views/kanban/kanban_record.js
@@ -87,6 +87,22 @@ var KanbanRecord = Widget.extend(WidgetAdapterMixin, {
// Public
//--------------------------------------------------------------------------
+ /**
+ * Check if a html content is empty. If there are only formatting tags with
+ * style attributes or a void content. Famous use case is
+ * '
' added by some web editor(s).
+ * Note that because the use of this method is limited, we ignore the cases
+ * like there's one tag in the content. In such case, even if it's the
+ * actual content, we consider it empty.
+ *
+ * @param {string} htmlContent
+ * @returns {boolean} true if no content found or if containing only formatting tags
+ */
+ isHtmlEmpty: function (htmlContent) {
+ let div = document.createElement('div');
+ div.innerHTML = htmlContent || "";
+ return div.innerText.trim() === "";
+ },
/**
* Re-renders the record with a new state
*
diff --git a/addons/web/static/src/scss/report.scss b/addons/web/static/src/scss/report.scss
index 8e29f0ceacb..27cbfe85e18 100644
--- a/addons/web/static/src/scss/report.scss
+++ b/addons/web/static/src/scss/report.scss
@@ -92,6 +92,17 @@ table {
}
}
+// TODO: investigate further for proper fix
+// By default there is 40px padding on the lists (both and
) and it is
+// also the case with HTML reports, but when PDFs are rendered with Wkhtmltopdf,
+// the padding is not applied on , it is strangely applied on
only.
+// So we simply remove the left padding, and apply left margin instead which
+// seems to do the job while not breaking layout in both html/pdf.
+ol {
+ margin-left: 40px;
+ padding-left: 0;
+}
+
// Wkhtmltopdf doesn't handle flexbox properly, both the content
// of columns and columns themselves does not wrap over new lines
// when needed: the font of the pdf will reduce to make the content
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index b548b5838e3..6385a430f16 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -7528,6 +7528,43 @@ QUnit.module('Views', {
kanban.destroy();
});
+
+ QUnit.test('kanban with isHtmlEmpty method', async function (assert) {
+ assert.expect(3);
+
+ this.data.product.fields.description = {string: 'Description', type: 'html'};
+ this.data.product.records.push(
+ {id: 11, display_name: "product 11", description: "hello"},
+ {id: 12, display_name: "product 12", description: "