[FIX] core,*: Fix display_name computations for new records.
As of https://github.com/odoo/odoo/pull/114024, `display_name` is implicit in every form view (see the use of addFieldDependencies in the Form controller). Therefore, when you create a new record for this model, it calls the first `onchange`, which will compute display_name for a new record (id without origin). Some `_compute_display_name` don't handle new records correctly and raise a traceback. These models are sometimes directly accessible: - Accounting > Account Group > New => Traceback - Contact > Contact Tags > New => Traceback Other models are inaccessible by default (no view to access or create a new record), but if someone creates a view for them with studio (or modifies an existing one to allow creation): - `crm.iap.lead.role` - `crm.iap.lead.seniority` - `chatbot.script.answer` - `payment.token` Change the code of `_compute_display_name` on these models to be more defensive and avoid (potential) tracebacks. Similarly, change the `convert_to_display_name` of `fields.Datetime` to take into account `None` value. closes odoo/odoo#139592 Related: odoo/enterprise#49721 Signed-off-by: Rémy Voet (ryv) <ryv@odoo.com>
This commit is contained in:
@@ -843,8 +843,7 @@ class AccountGroup(models.Model):
|
||||
prefix = group.code_prefix_start and str(group.code_prefix_start)
|
||||
if prefix and group.code_prefix_end != group.code_prefix_start:
|
||||
prefix += '-' + str(group.code_prefix_end)
|
||||
name = (prefix and (prefix + ' ') or '') + group.name
|
||||
group.display_name = name
|
||||
group.display_name = ' '.join(filter(None, [prefix, group.name]))
|
||||
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -19,4 +19,4 @@ class PeopleRole(models.Model):
|
||||
|
||||
def _compute_display_name(self):
|
||||
for role in self:
|
||||
role.display_name = role.name.replace('_', ' ').title()
|
||||
role.display_name = (role.name or '').replace('_', ' ').title()
|
||||
|
||||
@@ -19,4 +19,4 @@ class PeopleSeniority(models.Model):
|
||||
@api.depends('name')
|
||||
def _compute_display_name(self):
|
||||
for seniority in self:
|
||||
seniority.display_name = seniority.name.replace('_', ' ').title()
|
||||
seniority.display_name = (seniority.name or '').replace('_', ' ').title()
|
||||
|
||||
@@ -28,10 +28,12 @@ class ChatbotScriptAnswer(models.Model):
|
||||
return super()._compute_display_name()
|
||||
|
||||
for answer in self:
|
||||
answer_message = answer.script_step_id.message.replace('\n', ' ')
|
||||
shortened_message = textwrap.shorten(answer_message, width=26, placeholder=" [...]")
|
||||
|
||||
answer.display_name = f"{shortened_message}: {answer.name}"
|
||||
if answer.script_step_id:
|
||||
answer_message = answer.script_step_id.message.replace('\n', ' ')
|
||||
shortened_message = textwrap.shorten(answer_message, width=26, placeholder=" [...]")
|
||||
answer.display_name = f"{shortened_message}: {answer.name}"
|
||||
else:
|
||||
answer.display_name = answer.name
|
||||
|
||||
@api.model
|
||||
def _name_search(self, name, domain=None, operator='ilike', limit=None, order=None):
|
||||
|
||||
@@ -158,6 +158,9 @@ class PaymentToken(models.Model):
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
if not self.create_date:
|
||||
return ''
|
||||
|
||||
padding_length = max_length - len(self.payment_details or '')
|
||||
if not self.payment_details:
|
||||
create_date_str = self.create_date.strftime('%Y/%m/%d')
|
||||
|
||||
@@ -137,7 +137,7 @@ class PartnerCategory(models.Model):
|
||||
names = []
|
||||
current = category
|
||||
while current:
|
||||
names.append(current.name)
|
||||
names.append(current.name or "")
|
||||
current = current.parent_id
|
||||
category.display_name = ' / '.join(reversed(names))
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from . import test_basecase
|
||||
from . import test_cache
|
||||
from . import test_deprecation
|
||||
from . import test_db_cursor
|
||||
from . import test_display_name
|
||||
from . import test_expression
|
||||
from . import test_float
|
||||
from . import test_format_address_mixin
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import contextlib
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
|
||||
|
||||
IGNORE_MODEL_NAMES = {
|
||||
'ir.attachment',
|
||||
'test_new_api.attachment',
|
||||
'payment.link.wizard',
|
||||
'account.multicurrency.revaluation.wizard',
|
||||
'account_followup.manual_reminder',
|
||||
}
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestEveryModel(TransactionCase):
|
||||
|
||||
def test_display_name_new_record(self):
|
||||
for model_name in self.registry:
|
||||
model = self.env[model_name]
|
||||
if model._abstract or not model._auto or model_name in IGNORE_MODEL_NAMES:
|
||||
continue
|
||||
|
||||
with self.subTest(
|
||||
msg="`_compute_display_name` doesn't work with new record (first onchange call).",
|
||||
model=model_name,
|
||||
):
|
||||
# Check that the first onchange with display_name works on every models
|
||||
# OR it will fail anyway when people will use click on New
|
||||
fields_used = model._fields['display_name'].get_depends(model)[0]
|
||||
fields_used = [f.split('.', 1)[0] for f in fields_used]
|
||||
fields_spec = dict.fromkeys(fields_used + ['display_name'], {})
|
||||
with contextlib.suppress(UserError):
|
||||
model.onchange({}, [], fields_spec)
|
||||
+3
-2
@@ -2297,8 +2297,9 @@ class Datetime(Field):
|
||||
return self.from_string(value)
|
||||
|
||||
def convert_to_display_name(self, value, record):
|
||||
assert record, 'Record expected'
|
||||
return Datetime.to_string(Datetime.context_timestamp(record, Datetime.from_string(value)))
|
||||
if not value:
|
||||
return False
|
||||
return Datetime.to_string(Datetime.context_timestamp(record, value))
|
||||
|
||||
# http://initd.org/psycopg/docs/usage.html#binary-adaptation
|
||||
# Received data is returned as buffer (in Python 2) or memoryview (in Python 3).
|
||||
|
||||
Reference in New Issue
Block a user