Support instances as default value

Closes #13694
This commit is contained in:
Cédric Krier
2024-11-20 12:08:20 +01:00
parent 7e5ef52975
commit c03dff9352
10 changed files with 80 additions and 88 deletions
+2 -12
View File
@@ -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
+3 -12
View File
@@ -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
+2 -12
View File
@@ -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
+1
View File
@@ -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
+10 -4
View File
@@ -85,14 +85,20 @@ Class methods:
<model-ir.model.field>`.
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_<field name>()``.
Default values are defined by the returned value of each class method with
the pattern ``default_<field name>()``.
``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_<field
name>`` methods.
The ``default_rec_name`` key in the context can be used to define the value
of the :attr:`~Model._rec_name` field.
+2 -2
View File
@@ -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
+40 -20
View File
@@ -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):
+2 -1
View File
@@ -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']
+1 -1
View File
@@ -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):
+17 -24
View File
@@ -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):
'''