Ensure readonly fields are copied with the default value

Since 6c611c520744, the readonly of field is enforced so the copied value of
readonly field must be the default value to skip the check.
This commit is contained in:
Cédric Krier committed 2026-10-05 23:21:28 +02:00
1 parent 275cf3aadc
commit 197a988ef1
9 files changed
+34 -25

No files matched your search

+1 -1
View File
@@ -1274,7 +1274,7 @@ class Account(
else:
default = default.copy()
default.setdefault('template', None)
default.setdefault('deferrals', [])
default.setdefault('deferrals')
new_accounts = super().copy(accounts, default=default)
cls._rebuild_tree('parent', None, 0)
return new_accounts
+1 -1
View File
@@ -1571,7 +1571,7 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
default.setdefault('state', 'draft')
default.setdefault('move', None)
default.setdefault('reconciliation', None)
default.setdefault('reconciliations_delegated', [])
default.setdefault('reconciliations_delegated')
return super().copy(lines, default=default)
@classmethod
+3 -3
View File
@@ -728,12 +728,12 @@ class Asset(Workflow, ModelSQL, ModelView):
default = {}
else:
default = default.copy()
default.setdefault('lines', [])
default.setdefault('update_moves', [])
default.setdefault('lines')
default.setdefault('update_moves')
default.setdefault('number', None)
default.setdefault('supplier_invoice_line', None)
default.setdefault('move')
default.setdefault('revisions', [])
default.setdefault('revisions')
return super().copy(assets, default=default)
@classmethod
+2 -14
View File
@@ -282,10 +282,6 @@ class Payment(StripeCustomerMethodMixin, CheckoutMixin, metaclass=PoolMeta):
def default_stripe_capturable(cls):
return False
@classmethod
def default_stripe_idempotency_key(cls):
return uuid.uuid4().hex
@fields.depends('process_method',
'stripe_token', 'stripe_payment_intent_id',
'stripe_customer_source', 'stripe_customer_source_selection',
@@ -349,9 +345,7 @@ class Payment(StripeCustomerMethodMixin, CheckoutMixin, metaclass=PoolMeta):
if mode == 'create':
if 'stripe_idempotency_key' not in values:
# Ensure to get a different key for each record
# default methods are called only once
values['stripe_idempotency_key'] = (
cls.default_stripe_idempotency_key())
values['stripe_idempotency_key'] = uuid.uuid4().hex
return values
@classmethod
@@ -843,10 +837,6 @@ class Refund(Workflow, ModelSQL, ModelView):
def get_stripe_amount(self, name):
return int(self.amount * 10 ** self.currency.digits)
@classmethod
def default_stripe_idempotency_key(cls):
return uuid.uuid4().hex
@fields.depends('payment', '_parent_payment.currency')
def on_change_with_currency(self, name=None):
return self.payment.currency if self.payment else None
@@ -865,9 +855,7 @@ class Refund(Workflow, ModelSQL, ModelView):
if mode == 'create':
if 'stripe_idempotency_key' not in values:
# Ensure to get a different key for each record
# default methods are called only once
values['stripe_idempotency_key'] = (
cls.default_stripe_idempotency_key())
values['stripe_idempotency_key'] = uuid.uuid4().hex
return values
@classmethod
+5 -1
View File
@@ -968,6 +968,10 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
pass
return code
@classmethod
def default_eu_vat_valid(cls):
return False
def pre_validate(self):
super().pre_validate()
self.check_code()
@@ -1076,7 +1080,7 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
@classmethod
def copy(cls, identifiers, default=None):
default = default.copy() if default is not None else {}
default.setdefault('eu_vat_valid')
default.setdefault('eu_vat_valid', cls.default_eu_vat_valid())
default.setdefault('eu_vat_validated_at')
return super().copy(identifiers, default=default)
+1 -1
View File
@@ -223,7 +223,7 @@ class AdvancePaymentLine(ModelSQL, ModelView):
default = {}
else:
default = default.copy()
default.setdefault('invoice_lines', [])
default.setdefault('invoice_lines')
return super().copy(lines, default)
def create_invoice(self):
+5 -1
View File
@@ -128,6 +128,10 @@ class Sale(metaclass=PoolMeta):
return Configuration(1).get_multivalue(
'sale_task_creation_method', **pattern)
@classmethod
def default_tasks_to_create(cls):
return False
@classmethod
def check_method(cls, sales, field_names=None):
super().check_method(sales, field_names=field_names)
@@ -148,7 +152,7 @@ class Sale(metaclass=PoolMeta):
def copy(cls, sales, default=None):
default = default.copy() if default is not None else {}
default.setdefault('tasks_state')
default.setdefault('tasks_to_create')
default.setdefault('tasks_to_create', cls.default_tasks_to_create())
return super().copy(sales, default=default)
@classmethod
+8 -3
View File
@@ -114,12 +114,17 @@ class ModelSingleton(ModelStorage):
return res
@classmethod
def default_get(cls, fields_names, with_rec_name=True):
def default_get(
cls, fields_names=None, with_rec_name=True, with_default=True):
if fields_names is None:
fields_names = cls._fields.keys()
if '_timestamp' in fields_names:
fields_names = list(fields_names)
fields_names.remove('_timestamp')
default = super().default_get(fields_names,
with_rec_name=with_rec_name)
default = super().default_get(
fields_names,
with_rec_name=with_rec_name, with_default=with_default)
singleton = cls.get_singleton()
if singleton:
if with_rec_name:
+8
View File
@@ -927,6 +927,7 @@ class ModuleTestCase(DBTestCase):
continue
if not issubclass(model, ModelStorage):
continue
default_values = model.default_get(with_rec_name=False)
with self.subTest(model=mname):
model.copy([])
if copy.call_args:
@@ -939,6 +940,13 @@ class ModuleTestCase(DBTestCase):
fields = {
k.split('.', 1)[0] for k in default.keys()}
self.assertLessEqual(fields, klass._fields.keys())
for field_name in fields:
field = getattr(klass, field_name)
if field.readonly:
with self.subTest(field=field_name):
self.assertEqual(
default[field_name],
default_values.get(field_name))
copy.reset_mock()
@with_transaction()