From c03dff935234ebb6d612f540fc22145eea86728a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 20 Nov 2024 12:08:20 +0100 Subject: [PATCH] Support instances as default value Closes #13694 --- modules/purchase/purchase.py | 14 +----- modules/quality/quality.py | 15 ++----- modules/sale/sale.py | 14 +----- trytond/CHANGELOG | 1 + trytond/doc/ref/models.rst | 14 ++++-- trytond/trytond/ir/translation.py | 4 +- trytond/trytond/model/model.py | 60 ++++++++++++++++--------- trytond/trytond/model/modelsingleton.py | 3 +- trytond/trytond/res/user.py | 2 +- trytond/trytond/wizard/wizard.py | 41 +++++++---------- 10 files changed, 80 insertions(+), 88 deletions(-) diff --git a/modules/purchase/purchase.py b/modules/purchase/purchase.py index 2ddaa1ec80..37930cd5dd 100644 --- a/modules/purchase/purchase.py +++ b/modules/purchase/purchase.py @@ -15,8 +15,7 @@ from trytond.i18n import gettext from trytond.ir.attachment import AttachmentCopyMixin from trytond.ir.note import NoteCopyMixin from trytond.model import ( - Index, Model, ModelSQL, ModelView, Unique, Workflow, fields, - sequence_ordered) + Index, ModelSQL, ModelView, Unique, Workflow, fields, sequence_ordered) from trytond.model.exceptions import AccessError from trytond.modules.account.tax import TaxableMixin from trytond.modules.account_product.exceptions import AccountError @@ -2370,16 +2369,7 @@ class ModifyHeader(Wizard): purchase = self.get_purchase() values = {} for fieldname in fields: - value = getattr(purchase, fieldname) - if isinstance(value, Model): - if getattr(purchase.__class__, fieldname)._type == 'reference': - value = str(value) - else: - value = value.id - elif isinstance(value, (list, tuple)): - value = [r.id for r in value] - values[fieldname] = value - + values[fieldname] = getattr(purchase, fieldname) # Mimic an empty sale in draft state to get the fields' states right values['lines'] = [] return values diff --git a/modules/quality/quality.py b/modules/quality/quality.py index fa7d246d60..eb699dd27f 100644 --- a/modules/quality/quality.py +++ b/modules/quality/quality.py @@ -9,8 +9,8 @@ from sql.functions import CharLength from trytond.i18n import gettext, lazy_gettext from trytond.model import ( - DeactivableMixin, DictSchemaMixin, MatchMixin, Model, ModelSingleton, - ModelSQL, ModelStorage, ModelView, Workflow, dualmethod, fields) + DeactivableMixin, DictSchemaMixin, MatchMixin, ModelSingleton, ModelSQL, + ModelStorage, ModelView, Workflow, dualmethod, fields) from trytond.model.exceptions import AccessError, ButtonActionException from trytond.modules.company.model import ( CompanyMultiValueMixin, CompanyValueMixin, employee_field, reset_employee, @@ -820,16 +820,7 @@ class Inspect(Wizard): inspection = self.store.inspections[0] values = {} for fieldname in fields: - value = getattr(inspection, fieldname) - if isinstance(value, Model): - field = getattr(inspection.__class__, fieldname) - if field._type == 'reference': - value = str(value) - else: - value = value.id - elif isinstance(value, (list, tuple)): - value = [r.id for r in value] - values[fieldname] = value + values[fieldname] = getattr(inspection, fieldname) if 'points' in fields: # Convert ImmutableDict to dict diff --git a/modules/sale/sale.py b/modules/sale/sale.py index b33b3789de..9c92e401b4 100644 --- a/modules/sale/sale.py +++ b/modules/sale/sale.py @@ -15,8 +15,7 @@ from trytond.i18n import gettext from trytond.ir.attachment import AttachmentCopyMixin from trytond.ir.note import NoteCopyMixin from trytond.model import ( - Index, Model, ModelSQL, ModelView, Unique, Workflow, fields, - sequence_ordered) + Index, ModelSQL, ModelView, Unique, Workflow, fields, sequence_ordered) from trytond.model.exceptions import AccessError from trytond.modules.account.tax import TaxableMixin from trytond.modules.account_product.exceptions import AccountError @@ -2446,16 +2445,7 @@ class ModifyHeader(Wizard): sale = self.get_sale() values = {} for fieldname in fields: - value = getattr(sale, fieldname) - if isinstance(value, Model): - if getattr(sale.__class__, fieldname)._type == 'reference': - value = str(value) - else: - value = value.id - elif isinstance(value, (list, tuple)): - value = [r.id for r in value] - values[fieldname] = value - + values[fieldname] = getattr(sale, fieldname) # Mimic an empty sale in draft state to get the fields' states right values['lines'] = [] return values diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index 298e9b3d25..0d145ee792 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Support instances as default value * Add metadata columns to ModelSQL with table query * Add report data to header_key * Validate integer digits diff --git a/trytond/doc/ref/models.rst b/trytond/doc/ref/models.rst index 1e5033f761..43431ae295 100644 --- a/trytond/doc/ref/models.rst +++ b/trytond/doc/ref/models.rst @@ -85,14 +85,20 @@ Class methods: `. See :meth:`trytond.pool.PoolBase.__register__`. -.. classmethod:: Model.default_get(fields_names[, with_rec_name]) +.. classmethod:: Model.default_get(fields_names[[, with_rec_name], with_default]) Return a dictionary with the default values for each field in ``fields_names``. - Default values are defined by the returned value of each instance method - with the pattern ``default_()``. + Default values are defined by the returned value of each class method with + the pattern ``default_()``. - ``with_rec_name`` allow to add ``rec_name`` value for each many2one field. + ``with_rec_name`` allow to add ``rec_name`` value for each + :class:`~trytond.model.fields.Many2One`, + :class:`~trytond.model.fields.One2One`, + :class:`~trytond.model.fields.Reference` field. + + ``with_default`` allows to not use the value from the ``default_`` methods. The ``default_rec_name`` key in the context can be used to define the value of the :attr:`~Model._rec_name` field. diff --git a/trytond/trytond/ir/translation.py b/trytond/trytond/ir/translation.py index 935f8af834..3f65ef2b17 100644 --- a/trytond/trytond/ir/translation.py +++ b/trytond/trytond/ir/translation.py @@ -1514,7 +1514,7 @@ class TranslationUpdateStart(ModelView): ('code', '=', code), ('translatable', '=', True), ], limit=1) - return lang.id + return lang except ValueError: return None @@ -1739,7 +1739,7 @@ class TranslationExportStart(ModelView): domain = [('code', '=', code)] + cls.language.domain try: lang, = Lang.search(domain, limit=1) - return lang.id + return lang except ValueError: return None diff --git a/trytond/trytond/model/model.py b/trytond/trytond/model/model.py index fa12868587..a3fb609526 100644 --- a/trytond/trytond/model/model.py +++ b/trytond/trytond/model/model.py @@ -131,41 +131,61 @@ class Model(URLMixin, PoolBase, metaclass=ModelMeta): Translation.register_fields(cls, module_name) @classmethod - def default_get(cls, fields_names, with_rec_name=True): + def default_get(cls, fields_names, with_rec_name=True, with_default=True): ''' Return a dict with the default values for each field in fields_names. If with_rec_name is True, rec_name will be added. ''' pool = Pool() - value = {} + values = {} context = Transaction().context default_rec_name = context.get('default_rec_name') if (default_rec_name and cls._rec_name in cls._fields and cls._rec_name in fields_names): - value[cls._rec_name] = default_rec_name + values[cls._rec_name] = default_rec_name - # get the default values defined in the object for field_name in fields_names: + field = cls._fields[field_name] default_name = f'default_{field_name}' if field_name in cls._fields and default_name in context: - value[field_name] = context.get(default_name) - elif field_name in cls._defaults: - value[field_name] = cls._defaults[field_name]() - field = cls._fields[field_name] - if (field._type == 'boolean' - and field_name not in value): - value[field_name] = False - if (with_rec_name - and field._type in ('many2one',) - and value.get(field_name)): - Target = pool.get(field.model_name) - if 'rec_name' in Target._fields: - value.setdefault( - field_name + '.', {})['rec_name'] = Target( - value[field_name]).rec_name - return value + value = context.get(default_name) + elif with_default and field_name in cls._defaults: + value = cls._defaults[field_name]() + elif field._type == 'boolean': + value = False + else: + continue + if field._type in {'many2one', 'one2one', 'reference'}: + if isinstance(value, Model): + if with_rec_name and 'rec_name' in value._fields: + values.setdefault( + f'{field_name}.', {})['rec_name'] = value.rec_name + if field._type == 'reference': + value = str(value) + else: + value = int(value) + elif value and with_rec_name: + if field._type == 'reference': + target_name, target_id = value.split(',') + Target = pool.get(target_name) + else: + Target = field.get_target() + target_id = value + if 'rec_name' in Target._fields: + target = Target(target_id) + values.setdefault( + field_name + '.', {})['rec_name'] = target.rec_name + elif field._type in 'many2many': + if value: + value = [int(r) for r in value] + elif field._type == 'one2many': + if value: + if isinstance(value[0], Model): + value = [r._changed_values() for r in value] + values[field_name] = value + return values @classmethod def fields_get(cls, fields_names=None, level=0): diff --git a/trytond/trytond/model/modelsingleton.py b/trytond/trytond/model/modelsingleton.py index ee8ec2d241..0c2d7b431d 100644 --- a/trytond/trytond/model/modelsingleton.py +++ b/trytond/trytond/model/modelsingleton.py @@ -125,7 +125,8 @@ class ModelSingleton(ModelStorage): if with_rec_name: fields_names = fields_names[:] for field in fields_names[:]: - if cls._fields[field]._type in ('many2one',): + if cls._fields[field]._type in [ + 'many2one', 'one2one', 'reference']: fields_names.append(field + '.rec_name') default, = cls.read([singleton.id], fields_names=fields_names) del default['id'] diff --git a/trytond/trytond/res/user.py b/trytond/trytond/res/user.py index bf9b4ac10b..51cbe2836c 100644 --- a/trytond/trytond/res/user.py +++ b/trytond/trytond/res/user.py @@ -224,7 +224,7 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView): ('usage', '=', 'menu'), ], limit=1) if actions: - return actions[0].id + return actions[0] return None def get_pyson_menu(self, name): diff --git a/trytond/trytond/wizard/wizard.py b/trytond/trytond/wizard/wizard.py index bfb92c3268..1a822aacc6 100644 --- a/trytond/trytond/wizard/wizard.py +++ b/trytond/trytond/wizard/wizard.py @@ -89,35 +89,28 @@ class StateView(State): ''' pool = Pool() Model_ = pool.get(self.model_name) - defaults = Model_.default_get(fields) - default = getattr(wizard, 'default_%s' % state_name, None) - if default: - defaults.update(default(fields)) - self._complete_values(defaults) - return defaults + transaction = Transaction() + context = transaction.context + default_ctx = {} + if default_func := getattr(wizard, f'default_{state_name}', None): + for name, value in default_func(fields).items(): + name = f'default_{name}' + if name not in context: + default_ctx[name] = value + with transaction.set_context(default_ctx): + return Model_.default_get(fields) def get_values(self, wizard, state_name, fields): "Return values for the fields" - values = {} - value = getattr(wizard, 'value_%s' % state_name, None) - if value: - values.update(value(fields)) - self._complete_values(values) - return values - - def _complete_values(self, values): pool = Pool() Model_ = pool.get(self.model_name) - for field_name, value in list(values.items()): - if '.' in field_name: - continue - field = Model_._fields[field_name] - if value and field._type == 'many2one': - Target = field.get_target() - if 'rec_name' in Target._fields: - values.setdefault( - field_name + '.', {})['rec_name'] = Target( - value).rec_name + transaction = Transaction() + default_ctx = {} + if value_func := getattr(wizard, f'value_{state_name}', None): + for name, value in value_func(fields).items(): + default_ctx[f'default_{name}'] = value + with transaction.set_context(default_ctx): + return Model_.default_get(fields, with_default=False) def get_buttons(self, wizard, state_name): '''