Coverage for adhoc-cicd-odoo-odoo / odoo / orm / model_classes.py: 75%
251 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:05 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:05 +0000
1# Part of Odoo. See LICENSE file for full copyright and licensing details.
3from __future__ import annotations
5import logging
6import typing
8from collections import defaultdict
9from types import MappingProxyType
11from . import models
12from . import fields # must be imported after models
13from .utils import check_pg_name
14from odoo.exceptions import ValidationError
15from odoo.tools import (
16 OrderedSet,
17 LastOrderedSet,
18 discardattr,
19 frozendict,
20 sql,
21)
22from odoo.tools.translate import FIELD_TRANSLATE
24if typing.TYPE_CHECKING:
25 from odoo.api import Environment
26 from odoo.fields import Field
27 from odoo.models import BaseModel
28 from odoo.modules.registry import Registry
30_logger = logging.getLogger('odoo.registry')
32# THE MODEL DEFINITIONS, MODEL CLASSES, AND MODEL INSTANCES
33#
34# The framework deals with two kinds of classes for models: the "model
35# definitions" and the "model classes".
36#
37# The "model definitions" are the classes defined in modules source code: they
38# define models and extend them. Those classes are essentially "static", for
39# whatever that means in Python. The only exception is custom models: their
40# model definition is created dynamically.
41#
42# The "model classes" are the ones you find in the registry. The recordsets of
43# a model actually are instances of its model class. The "model class" of a
44# model is created dynamically when the registry is built. It inherits (in the
45# Python sense) from all the model definitions of the model, and possibly other
46# model classes (when the model inherits from another model). It also carries
47# model metadata inferred from its parent classes.
48#
49#
50# THE MODEL CLASSES
51#
52# In the simplest case, a model class inherits from all the classes that define
53# the model in a flat hierarchy. Consider the definitions of model 'a' below.
54# The model class of 'a' inherits from the model definitions A1, A2, A3, in
55# reverse order, to match the expected overriding order. The model class
56# carries inferred metadata that is shared between all the recordsets of that
57# model for a given registry.
58#
59# class A(Model): # A1 Model
60# _name = 'a' / | \
61# A3 A2 A1 <- model definitions
62# class A(Model): # A2 \ | /
63# _inherit = 'a' a <- model class: registry['a']
64# |
65# class A(Model): # A3 records <- model instances, like env['a']
66# _inherit = 'a'
67#
68# Note that when the model inherits from another model, we actually make the
69# model classes inherit from each other, so that extensions to an inherited
70# model are visible in the model class of the child model, like in the
71# following example.
72#
73# class A(Model): # A1
74# _name = 'a' Model
75# / / \ \
76# class B(Model): # B1 / / \ \
77# _name = 'b' / A2 A1 \
78# B2 \ / B1
79# class B(Model): # B2 \ \ / /
80# _inherit = ['a', 'b'] \ a /
81# \ | /
82# class A(Model): # A2 \ | /
83# _inherit = 'a' b
84#
85# To be more explicit, the parent classes of model 'a' are (A2, A1), and the
86# ones of model 'b' are (B2, a, B1). Consequently, the MRO of model 'a' is
87# [a, A2, A1, Model] while the MRO of 'b' is [b, B2, a, A2, A1, B1, Model].
88#
89#
90# THE FIELDS OF A MODEL
91#
92# The fields of a model are given by the model's definitions, inherited models
93# ('_inherit' and '_inherits') and other parties, like custom fields. Note that
94# a field can be partially overridden when it appears on several definitions of
95# its model. In that case, the field's final definition depends on the
96# presence or absence of each model definition, which itself depends on the
97# modules loaded in the registry.
98#
99# By design, the model class has access to all the fields on its model
100# definitions. When possible, the field is used directly from its model
101# definition. There are a number of cases where the field cannot be used
102# directly:
103# - the field is related (and bits may not be shared);
104# - the field is overridden on model definitions;
105# - the field is defined on another model (and accessible by mixin).
106#
107# The last case prevents sharing the field across registries, because the field
108# object is specific to a model, and is used as a key in several key
109# dictionaries, like the record cache and pending computations.
110#
111# Setting up a field on its model definition helps saving memory and time.
112# Indeed, when sharing is possible, the field's setup is almost entirely done
113# where the field was defined. It is thus done when the model definition was
114# created, and it may be reused across registries.
115#
116# In the example below, the field 'foo' appears once on its model definition.
117# Assuming that it is not related, that field can be set up directly on its
118# model definition. If the model appears in several registries, the
119# field 'foo' is effectively shared across registries.
120#
121# class A1(Model): Model
122# _name = 'a' / \
123# foo = ... / \
124# bar = ... A2 A1
125# bar foo, bar
126# class A2(Model): \ /
127# _inherit = 'a' \ /
128# bar = ... a
129# bar
130#
131# On the other hand, the field 'bar' is overridden in its model definitions. In
132# that case, the framework recreates the field on the model class, which is
133# never shared across registries. The field's setup will be based on its
134# definitions, and will thus not be shared across registries.
135#
136# The so-called magic fields ('id', 'display_name', ...) used to be added on
137# model classes. But doing so prevents them from being shared. So instead,
138# we add them on definition classes that define a model without extending it.
139# This increases the number of fields that are shared across registries.
142def is_model_definition(cls: type) -> bool:
143 """ Return whether ``cls`` is a model definition class. """
144 return isinstance(cls, models.MetaModel) and getattr(cls, 'pool', None) is None
147def is_model_class(cls: type) -> bool:
148 """ Return whether ``cls`` is a model registry class. """
149 return getattr(cls, 'pool', None) is not None
152def add_to_registry(registry: Registry, model_def: type[BaseModel]) -> type[BaseModel]:
153 """ Add a model definition to the given registry, and return its
154 corresponding model class. This function creates or extends a model class
155 for the given model definition.
156 """
157 assert is_model_definition(model_def)
159 if hasattr(model_def, '_constraints'): 159 ↛ 160line 159 didn't jump to line 160 because the condition on line 159 was never true
160 _logger.warning("Model attribute '_constraints' is no longer supported, "
161 "please use @api.constrains on methods instead.")
162 if hasattr(model_def, '_sql_constraints'): 162 ↛ 163line 162 didn't jump to line 163 because the condition on line 162 was never true
163 _logger.warning("Model attribute '_sql_constraints' is no longer supported, "
164 "please define model.Constraint on the model.")
166 # all models except 'base' implicitly inherit from 'base'
167 name = model_def._name
168 parent_names = list(model_def._inherit)
169 if name != 'base':
170 parent_names.append('base')
172 # create or retrieve the model's class
173 if name in parent_names:
174 if name not in registry: 174 ↛ 175line 174 didn't jump to line 175 because the condition on line 174 was never true
175 raise TypeError(f"Model {name!r} does not exist in registry.")
176 model_cls = registry[name]
177 _check_model_extension(model_cls, model_def)
178 else:
179 model_cls = type(name, (model_def,), {
180 'pool': registry, # this makes it a model class
181 '_name': name,
182 '_register': False,
183 '_original_module': model_def._module,
184 '_inherit_module': {}, # map parent to introducing module
185 '_inherit_children': OrderedSet(), # names of children models
186 '_inherits_children': set(), # names of children models
187 '_fields__': {}, # populated in _setup()
188 '_table_objects': frozendict(), # populated in _setup()
189 })
190 model_cls._fields = MappingProxyType(model_cls._fields__)
192 # determine all the classes the model should inherit from
193 bases = LastOrderedSet([model_def])
194 for parent_name in parent_names:
195 if parent_name not in registry: 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true
196 raise TypeError(f"Model {name!r} inherits from non-existing model {parent_name!r}.")
197 parent_cls = registry[parent_name]
198 if parent_name == name:
199 for base in parent_cls._base_classes__:
200 bases.add(base)
201 else:
202 _check_model_parent_extension(model_cls, model_def, parent_cls)
203 bases.add(parent_cls)
204 model_cls._inherit_module[parent_name] = model_def._module
205 parent_cls._inherit_children.add(name)
207 # model_cls.__bases__ must be assigned those classes; however, this
208 # operation is quite slow, so we do it once in method _prepare_setup()
209 model_cls._base_classes__ = tuple(bases)
211 # determine the attributes of the model's class
212 _init_model_class_attributes(model_cls)
214 check_pg_name(model_cls._table)
216 # Transience
217 if model_cls._transient and not model_cls._log_access: 217 ↛ 218line 217 didn't jump to line 218 because the condition on line 217 was never true
218 raise TypeError(
219 "TransientModels must have log_access turned on, "
220 "in order to implement their vacuum policy"
221 )
223 # update the registry after all checks have passed
224 registry[name] = model_cls
226 # mark all impacted models for setup
227 for model_name in registry.descendants([name], '_inherit', '_inherits'):
228 registry[model_name]._setup_done__ = False
230 return model_cls
233def _check_model_extension(model_cls: type[BaseModel], model_def: type[BaseModel]):
234 """ Check whether ``model_cls`` can be extended with ``model_def``. """
235 if model_cls._abstract and not model_def._abstract: 235 ↛ 236line 235 didn't jump to line 236 because the condition on line 235 was never true
236 raise TypeError(
237 f"{model_def} transforms the abstract model {model_cls._name!r} into a non-abstract model. "
238 "That class should either inherit from AbstractModel, or set a different '_name'."
239 )
240 if model_cls._transient != model_def._transient: 240 ↛ 241line 240 didn't jump to line 241 because the condition on line 240 was never true
241 if model_cls._transient:
242 raise TypeError(
243 f"{model_def} transforms the transient model {model_cls._name!r} into a non-transient model. "
244 "That class should either inherit from TransientModel, or set a different '_name'."
245 )
246 else:
247 raise TypeError(
248 f"{model_def} transforms the model {model_cls._name!r} into a transient model. "
249 "That class should either inherit from Model, or set a different '_name'."
250 )
253def _check_model_parent_extension(model_cls: type[BaseModel], model_def: type[BaseModel], parent_cls: type[BaseModel]):
254 """ Check whether ``model_cls`` can inherit from ``parent_cls``. """
255 if model_cls._abstract and not parent_cls._abstract: 255 ↛ 256line 255 didn't jump to line 256 because the condition on line 255 was never true
256 raise TypeError(
257 f"In {model_def}, abstract model {model_cls._name!r} cannot inherit from non-abstract model {parent_cls._name!r}."
258 )
261def _init_model_class_attributes(model_cls: type[BaseModel]):
262 """ Initialize model class attributes. """
263 assert is_model_class(model_cls)
265 model_cls._description = model_cls._name
266 model_cls._table = model_cls._name.replace('.', '_')
267 model_cls._log_access = model_cls._auto
268 inherits = {}
269 depends = {}
271 for base in reversed(model_cls._base_classes__):
272 if is_model_definition(base):
273 # the following attributes are not taken from registry classes
274 if model_cls._name not in base._inherit and not base._description: 274 ↛ 275line 274 didn't jump to line 275 because the condition on line 274 was never true
275 _logger.warning("The model %s has no _description", model_cls._name)
276 model_cls._description = base._description or model_cls._description
277 model_cls._table = base._table or model_cls._table
278 model_cls._log_access = getattr(base, '_log_access', model_cls._log_access)
280 inherits.update(base._inherits)
282 for mname, fnames in base._depends.items():
283 depends.setdefault(mname, []).extend(fnames)
285 # avoid assigning an empty dict to save memory
286 if inherits:
287 model_cls._inherits = inherits
288 if depends:
289 model_cls._depends = depends
291 # update _inherits_children of parent models
292 registry = model_cls.pool
293 for parent_name in model_cls._inherits:
294 registry[parent_name]._inherits_children.add(model_cls._name)
296 # recompute attributes of _inherit_children models
297 for child_name in model_cls._inherit_children:
298 _init_model_class_attributes(registry[child_name])
301def setup_model_classes(env: Environment):
302 registry = env.registry
304 # we must setup ir.model before adding manual fields because _add_manual_models may
305 # depend on behavior that is implemented through overrides, such as is_mail_thread which
306 # is implemented through an override to env['ir.model']._instanciate_attrs
307 _prepare_setup(registry['ir.model'])
309 # add manual models
310 if registry._init_modules:
311 _add_manual_models(env)
313 # prepare the setup on all models
314 models_classes = list(registry.values())
315 for model_cls in models_classes:
316 _prepare_setup(model_cls)
318 # do the actual setup
319 for model_cls in models_classes:
320 _setup(model_cls, env)
322 for model_cls in models_classes:
323 _setup_fields(model_cls, env)
325 for model_cls in models_classes:
326 model_cls(env, (), ())._post_model_setup__()
329def _prepare_setup(model_cls: type[BaseModel]):
330 """ Prepare the setup of the model. """
331 if model_cls._setup_done__:
332 assert model_cls.__bases__ == model_cls._base_classes__
333 return
335 # changing base classes is costly, do it only when necessary
336 if model_cls.__bases__ != model_cls._base_classes__:
337 model_cls.__bases__ = model_cls._base_classes__
339 # reset those attributes on the model's class for _setup_fields() below
340 for attr in ('_rec_name', '_active_name'):
341 discardattr(model_cls, attr)
343 # reset properties memoized on model_cls
344 model_cls._constraint_methods = models.BaseModel._constraint_methods
345 model_cls._ondelete_methods = models.BaseModel._ondelete_methods
346 model_cls._onchange_methods = models.BaseModel._onchange_methods
349def _setup(model_cls: type[BaseModel], env: Environment):
350 """ Determine all the fields of the model. """
351 if model_cls._setup_done__:
352 return
354 # the classes that define this model, i.e., the ones that are not
355 # registry classes; the purpose of this attribute is to behave as a
356 # cache of [c for c in model_cls.mro() if not is_model_class(c))], which
357 # is heavily used in function fields.resolve_mro()
358 model_cls._model_classes__ = tuple(c for c in model_cls.mro() if getattr(c, 'pool', None) is None)
360 # 1. determine the proper fields of the model: the fields defined on the
361 # class and magic fields, not the inherited or custom ones
363 # retrieve fields from parent classes, and duplicate them on model_cls to
364 # avoid clashes with inheritance between different models
365 for name in model_cls._fields:
366 discardattr(model_cls, name)
367 model_cls._fields__.clear()
369 # collect the definitions of each field (base definition + overrides)
370 definitions = defaultdict(list)
371 for cls in reversed(model_cls._model_classes__):
372 # this condition is an optimization of is_model_definition(cls)
373 if isinstance(cls, models.MetaModel):
374 for field in cls._field_definitions:
375 definitions[field.name].append(field)
377 for name, fields_ in definitions.items():
378 if f'{model_cls._name}.{name}' in model_cls.pool._database_translated_fields: 378 ↛ 382line 378 didn't jump to line 382 because the condition on line 378 was never true
379 # the field is currently translated in the database; ensure the
380 # field is translated to avoid converting its column to varchar
381 # and losing data
382 translate = next((
383 field._args__['translate'] for field in reversed(fields_) if 'translate' in field._args__
384 ), False)
385 if not translate:
386 field_translate = FIELD_TRANSLATE.get(
387 model_cls.pool._database_translated_fields[f'{model_cls._name}.{name}'],
388 True
389 )
390 # patch the field definition by adding an override
391 _logger.debug("Patching %s.%s with translate=True", model_cls._name, name)
392 fields_.append(type(fields_[0])(translate=field_translate))
393 if f'{model_cls._name}.{name}' in model_cls.pool._database_company_dependent_fields: 393 ↛ 397line 393 didn't jump to line 397 because the condition on line 393 was never true
394 # the field is currently company dependent in the database; ensure
395 # the field is company dependent to avoid converting its column to
396 # the base data type
397 company_dependent = next((
398 field._args__['company_dependent'] for field in reversed(fields_) if 'company_dependent' in field._args__
399 ), False)
400 if not company_dependent:
401 # validate column type again in case the column type is changed by upgrade script
402 rows = env.execute_query(sql.SQL(
403 'SELECT data_type FROM information_schema.columns'
404 ' WHERE table_name = %s AND column_name = %s AND table_schema = current_schema',
405 model_cls._table, name,
406 ))
407 if rows and rows[0][0] == 'jsonb':
408 # patch the field definition by adding an override
409 _logger.warning("Patching %s.%s with company_dependent=True", model_cls._name, name)
410 fields_.append(type(fields_[0])(company_dependent=True))
411 if len(fields_) == 1 and fields_[0]._direct and fields_[0].model_name == model_cls._name:
412 model_cls._fields__[name] = fields_[0]
413 else:
414 Field = type(fields_[-1])
415 add_field(model_cls, name, Field(_base_fields__=tuple(fields_)))
417 # 2. add manual fields
418 if model_cls.pool._init_modules:
419 _add_manual_fields(model_cls, env)
421 # 3. make sure that parent models determine their own fields, then add
422 # inherited fields to model_cls
423 _check_inherits(model_cls)
424 for parent_name in model_cls._inherits:
425 _setup(model_cls.pool[parent_name], env)
426 _add_inherited_fields(model_cls)
428 # 4. initialize more field metadata
429 model_cls._setup_done__ = True
431 for field in model_cls._fields.values():
432 field.prepare_setup()
434 # 5. determine and validate rec_name
435 if model_cls._rec_name:
436 assert model_cls._rec_name in model_cls._fields, \
437 "Invalid _rec_name=%r for model %r" % (model_cls._rec_name, model_cls._name)
438 elif 'name' in model_cls._fields:
439 model_cls._rec_name = 'name'
440 elif model_cls._custom and 'x_name' in model_cls._fields: 440 ↛ 441line 440 didn't jump to line 441 because the condition on line 440 was never true
441 model_cls._rec_name = 'x_name'
443 # 6. determine and validate active_name
444 if model_cls._active_name:
445 assert (model_cls._active_name in model_cls._fields
446 and model_cls._active_name in ('active', 'x_active')), \
447 ("Invalid _active_name=%r for model %r; only 'active' and "
448 "'x_active' are supported and the field must be present on "
449 "the model") % (model_cls._active_name, model_cls._name)
450 elif 'active' in model_cls._fields:
451 model_cls._active_name = 'active'
452 elif 'x_active' in model_cls._fields: 452 ↛ 453line 452 didn't jump to line 453 because the condition on line 452 was never true
453 model_cls._active_name = 'x_active'
455 # 7. determine table objects
456 assert not model_cls._table_object_definitions, "model_cls is a registry model"
457 model_cls._table_objects = frozendict({
458 cons.full_name(model_cls): cons
459 for cls in reversed(model_cls._model_classes__)
460 if isinstance(cls, models.MetaModel)
461 for cons in cls._table_object_definitions
462 })
465def _check_inherits(model_cls: type[BaseModel]):
466 for comodel_name, field_name in model_cls._inherits.items():
467 field = model_cls._fields.get(field_name)
468 if not field or field.type != 'many2one': 468 ↛ 469line 468 didn't jump to line 469 because the condition on line 468 was never true
469 raise TypeError(
470 f"Missing many2one field definition for _inherits reference {field_name!r} in model {model_cls._name!r}. "
471 f"Add a field like: {field_name} = fields.Many2one({comodel_name!r}, required=True, ondelete='cascade')"
472 )
473 if not (field.delegate and field.required and (field.ondelete or "").lower() in ("cascade", "restrict")): 473 ↛ 474line 473 didn't jump to line 474 because the condition on line 473 was never true
474 raise TypeError(
475 f"Field definition for _inherits reference {field_name!r} in {model_cls._name!r} "
476 "must be marked as 'delegate', 'required' with ondelete='cascade' or 'restrict'"
477 )
480def _add_inherited_fields(model_cls: type[BaseModel]):
481 """ Determine inherited fields. """
482 if model_cls._abstract or not model_cls._inherits:
483 return
485 # determine which fields can be inherited
486 to_inherit = {
487 name: (parent_fname, field)
488 for parent_model_name, parent_fname in model_cls._inherits.items()
489 for name, field in model_cls.pool[parent_model_name]._fields.items()
490 }
492 # add inherited fields that are not redefined locally
493 for name, (parent_fname, field) in to_inherit.items():
494 if name not in model_cls._fields:
495 # inherited fields are implemented as related fields, with the
496 # following specific properties:
497 # - reading inherited fields should not bypass access rights
498 # - copy inherited fields iff their original field is copied
499 field_cls = type(field)
500 add_field(model_cls, name, field_cls(
501 inherited=True,
502 inherited_field=field,
503 related=f"{parent_fname}.{name}",
504 related_sudo=False,
505 copy=field.copy,
506 readonly=field.readonly,
507 export_string_translation=field.export_string_translation,
508 ))
511def _setup_fields(model_cls: type[BaseModel], env: Environment):
512 """ Setup the fields, except for recomputation triggers. """
513 bad_fields = []
514 many2one_company_dependents = model_cls.pool.many2one_company_dependents
515 model = model_cls(env, (), ())
516 for name, field in model_cls._fields.items():
517 try:
518 field.setup(model)
519 except Exception:
520 if field.base_field.manual:
521 # Something goes wrong when setup a manual field.
522 # This can happen with related fields using another manual many2one field
523 # that hasn't been loaded because the comodel does not exist yet.
524 # This can also be a manual function field depending on not loaded fields yet.
525 bad_fields.append(name)
526 continue
527 raise
528 if field.type == 'many2one' and field.company_dependent:
529 many2one_company_dependents.add(field.comodel_name, field)
531 for name in bad_fields: 531 ↛ 532line 531 didn't jump to line 532 because the loop on line 531 never started
532 pop_field(model_cls, name)
535def _add_manual_models(env: Environment):
536 """ Add extra models to the registry. """
537 # clean up registry first
538 for name, model_cls in list(env.registry.items()):
539 if model_cls._custom: 539 ↛ 540line 539 didn't jump to line 540 because the condition on line 539 was never true
540 del env.registry.models[name]
541 # remove the model's name from its parents' _inherit_children
542 for parent_cls in model_cls.__bases__:
543 if hasattr(parent_cls, 'pool'):
544 parent_cls._inherit_children.discard(name)
546 # we cannot use self._fields to determine translated fields, as it has not been set up yet
547 env.cr.execute("SELECT *, name->>'en_US' AS name FROM ir_model WHERE state = 'manual'")
548 for model_data in env.cr.dictfetchall(): 548 ↛ 549line 548 didn't jump to line 549 because the loop on line 548 never started
549 attrs = env['ir.model']._instanciate_attrs(model_data)
551 # adapt _auto and _log_access if necessary
552 table_name = model_data["model"].replace(".", "_")
553 table_kind = sql.table_kind(env.cr, table_name)
554 if table_kind not in (sql.TableKind.Regular, None):
555 _logger.info(
556 "Model %r is backed by table %r which is not a regular table (%r), disabling automatic schema management",
557 model_data["model"], table_name, table_kind,
558 )
559 attrs['_auto'] = False
560 env.cr.execute(
561 """ SELECT a.attname
562 FROM pg_attribute a
563 JOIN pg_class t ON a.attrelid = t.oid AND t.relname = %s
564 WHERE a.attnum > 0 -- skip system columns
565 AND t.relnamespace = current_schema::regnamespace """,
566 [table_name]
567 )
568 columns = {colinfo[0] for colinfo in env.cr.fetchall()}
569 attrs['_log_access'] = set(models.LOG_ACCESS_COLUMNS) <= columns
571 model_def = type('CustomDefinitionModel', (models.Model,), attrs)
572 add_to_registry(env.registry, model_def)
575def _add_manual_fields(model_cls: type[BaseModel], env: Environment):
576 """ Add extra fields on model. """
577 IrModelFields = env['ir.model.fields']
579 fields_data = IrModelFields._get_manual_field_data(model_cls._name)
580 for name, field_data in fields_data.items():
581 if name not in model_cls._fields and field_data['state'] == 'manual': 581 ↛ 580line 581 didn't jump to line 580 because the condition on line 581 was always true
582 try:
583 attrs = IrModelFields._instanciate_attrs(field_data)
584 if attrs: 584 ↛ 580line 584 didn't jump to line 580 because the condition on line 584 was always true
585 field = fields.Field._by_type__[field_data['ttype']](**attrs)
586 add_field(model_cls, name, field)
587 except Exception:
588 _logger.exception("Failed to load field %s.%s: skipped", model_cls._name, field_data['name'])
591def add_field(model_cls: type[BaseModel], name: str, field: Field):
592 """ Add the given ``field`` under the given ``name`` on the model class of the given ``model``. """
593 # Assert the name is an existing field in the model, or any model in the _inherits
594 # or a custom field (starting by `x_`)
595 is_class_field = any(
596 isinstance(getattr(model, name, None), fields.Field)
597 for model in [model_cls] + [model_cls.pool[inherit] for inherit in model_cls._inherits]
598 )
599 if not (is_class_field or model_cls.pool['ir.model.fields']._is_manual_name(None, name)): 599 ↛ 600line 599 didn't jump to line 600 because the condition on line 599 was never true
600 raise ValidationError( # pylint: disable=missing-gettext
601 f"The field `{name}` is not defined in the `{model_cls._name}` Python class and does not start with 'x_'"
602 )
604 # Assert the attribute to assign is a Field
605 if not isinstance(field, fields.Field): 605 ↛ 606line 605 didn't jump to line 606 because the condition on line 605 was never true
606 raise ValidationError("You can only add `fields.Field` objects to a model fields") # pylint: disable=missing-gettext
608 if not isinstance(getattr(model_cls, name, field), fields.Field): 608 ↛ 609line 608 didn't jump to line 609 because the condition on line 608 was never true
609 _logger.warning("In model %r, field %r overriding existing value", model_cls._name, name)
610 setattr(model_cls, name, field)
611 field._toplevel = True
612 field.__set_name__(model_cls, name)
613 # add field as an attribute and in model_cls._fields__ (for reflection)
614 model_cls._fields__[name] = field
617def pop_field(model_cls: type[BaseModel], name: str) -> Field | None:
618 """ Remove the field with the given ``name`` from the model class of ``model``. """
619 field = model_cls._fields__.pop(name, None)
620 discardattr(model_cls, name)
621 if model_cls._rec_name == name:
622 # fixup _rec_name and display_name's dependencies
623 model_cls._rec_name = None
624 if model_cls.display_name in model_cls.pool.field_depends:
625 model_cls.pool.field_depends[model_cls.display_name] = tuple(
626 dep for dep in model_cls.pool.field_depends[model_cls.display_name] if dep != name
627 )
628 return field