Always synchronize XML data with database

Closes #13668
This commit is contained in:
Cédric Krier
2024-11-11 18:24:06 +01:00
parent efab50227a
commit fabb5340e9
11 changed files with 87 additions and 456 deletions
+2 -6
View File
@@ -56,13 +56,9 @@ def create_chart(
if not company:
company = get_company(config=config)
module, xml_id = chart.split('.')
data, = ModelData.find([
('module', '=', module),
('fs_id', '=', xml_id),
], limit=1)
chart_id = ModelData.get_id(chart, config.context)
account_template = AccountTemplate(data.db_id)
account_template = AccountTemplate(chart_id)
create_chart = Wizard('account.create_chart', config=config)
create_chart.execute('account')
+2 -5
View File
@@ -13,12 +13,9 @@ def create_chart(company=None, config=None):
if not company:
company = get_company(config=config)
data, = ModelData.find([
('module', '=', 'account_fr'),
('fs_id', '=', 'root'),
], limit=1)
chart_id = ModelData.get_id('account_fr', 'root', config.context)
account_template = AccountTemplate(data.db_id)
account_template = AccountTemplate(chart_id)
create_chart = Wizard('account.create_chart', config=config)
create_chart.execute('account')
+1
View File
@@ -1,3 +1,4 @@
* Always synchronize XML data with database
* Convert None as empty string when searching on Char or Text
* Support instances as default value
* Add metadata columns to ModelSQL with table query
@@ -122,18 +122,6 @@ Model Data
The *Model Data* keeps track of the :class:`~trytond.model.ModelStorage`
records created by modules via `XML files <topics-modules-tryton-cfg>`.
If a *Model Data* is out of sync (i.e. the values in the database do not match
the values in the XML definition), it is possible to force synchronisation
using the :guilabel:`Sync` button.
.. seealso::
Model Data are found by opening the main menu item:
|Administration --> Models --> Data|__
.. |Administration --> Models --> Data| replace:: :menuselection:`Administration --> Models --> Data`
__ https://demo.tryton.org/model/ir.model.data
.. _model-ir.model.log:
+50 -243
View File
@@ -10,7 +10,6 @@ from xml import sax
from trytond import __version__
from trytond.pyson import CONTEXT, PYSONEncoder
from trytond.tools import grouped_slice
from trytond.transaction import Transaction, inactive_records
logger = logging.getLogger(__name__)
@@ -332,12 +331,10 @@ class FS2DBAccessor:
Provide some helper function to ease cache access and management.
"""
def __init__(self, ModelData, pool):
self.fs2db = {}
def __init__(self, ModelData):
self.fs2db = defaultdict(lambda: defaultdict(dict))
self.fetched_modules = []
self.ModelData = ModelData
self.browserecord = {}
self.pool = pool
def get(self, module, fs_id):
if module not in self.fetched_modules:
@@ -349,71 +346,20 @@ class FS2DBAccessor:
self.fetch_new_module(module)
return fs_id in self.fs2db[module]
def get_browserecord(self, module, model_name, db_id):
def set(self, module, fs_id, record):
# We call the prefetch function here to.
# Like that we are sure not to erase data when get is called.
if module not in self.fetched_modules:
self.fetch_new_module(module)
if model_name in self.browserecord[module] \
and db_id in self.browserecord[module][model_name]:
return self.browserecord[module][model_name][db_id]
return None
def set(self, module, fs_id, values):
"""
Whe call the prefetch function here to. Like that whe are sure
not to erase data when get is called.
"""
if module not in self.fetched_modules:
self.fetch_new_module(module)
if fs_id not in self.fs2db[module]:
self.fs2db[module][fs_id] = {}
fs2db_val = self.fs2db[module][fs_id]
for key, val in values.items():
fs2db_val[key] = val
def reset_browsercord(self, module, model_name, ids=None):
if module not in self.fetched_modules:
return
self.browserecord[module].setdefault(model_name, {})
Model = self.pool.get(model_name)
if not ids:
ids = list(self.browserecord[module][model_name].keys())
models = Model.browse(ids)
for model in models:
if model.id in self.browserecord[module][model_name]:
for cache in Transaction().cache.values():
if model_name in cache:
cache[model_name].pop(model.id, None)
self.browserecord[module][model_name][model.id] = model
self.fs2db[module][fs_id] = record
def fetch_new_module(self, module):
self.fs2db[module] = {}
module_data_ids = self.ModelData.search([
records = self.ModelData.search([
('module', '=', module),
], order=[('db_id', 'ASC')])
record_ids = {}
for rec in self.ModelData.browse(module_data_ids):
self.fs2db[rec.module][rec.fs_id] = {
"db_id": rec.db_id, "model": rec.model,
"id": rec.id, "values": rec.values
}
record_ids.setdefault(rec.model, [])
record_ids[rec.model].append(rec.db_id)
self.browserecord[module] = {}
for model_name in record_ids.keys():
try:
Model = self.pool.get(model_name)
except KeyError:
continue
self.browserecord[module][model_name] = {}
for sub_record_ids in grouped_slice(record_ids[model_name]):
with inactive_records():
records = Model.search([
('id', 'in', list(sub_record_ids)),
], order=[('id', 'ASC')])
for model in records:
self.browserecord[module][model_name][model.id] = model
],
order=[('db_id', 'ASC')])
for record in records:
self.fs2db[record.module][record.fs_id] = record
self.fetched_modules.append(module)
@@ -426,7 +372,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
self.pool = pool
self.module = module
self.ModelData = pool.get('ir.model.data')
self.fs2db = FS2DBAccessor(self.ModelData, pool)
self.fs2db = FS2DBAccessor(self.ModelData)
self.to_delete = self.populate_to_delete()
self.noupdate = None
self.module_state = module_state
@@ -508,13 +454,12 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
for model, values in self.grouped_creations.items():
self.create_records(model, values.values(), values.keys())
self.grouped_creations.clear()
for key, actions in self.grouped_write.items():
module, model = key
self.write_records(module, model, *actions)
for model, actions in self.grouped_write.items():
self.write_records(model, *actions)
self.grouped_write.clear()
if name == 'data' and self.grouped_model_data:
self.ModelData.write(*self.grouped_model_data)
del self.grouped_model_data[:]
self.ModelData.save(self.grouped_model_data)
self.grouped_model_data.clear()
# Closing tag found, if we are in a delegation the handler
# know what to do:
@@ -538,8 +483,8 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
if self.fs2db.get(module, xml_id) is None:
raise ParsingError("%s.%s not found" % (module, xml_id))
value = self.fs2db.get(module, xml_id)
return value['model'], value["db_id"]
record = self.fs2db.get(module, xml_id)
return record.model, record.db_id
@staticmethod
def _clean_value(key, record):
@@ -584,112 +529,25 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
raise ValueError("missing fs_id")
if '.' in fs_id:
assert len(fs_id.split('.')) == 2, ('"%s" contains too many dots. '
'file system ids should contain ot most one dot ! '
'These are used to refer to other modules data, '
'as in module.reference_id' % (fs_id))
module, fs_id = fs_id.split('.')
if not self.fs2db.get(module, fs_id):
raise ParsingError("%s.%s not found" % (module, fs_id))
Model = self.pool.get(model)
# Remove this record from the to_delete list.
# This means that the corresponding record have been found.
self.to_delete.discard(fs_id)
if self.fs2db.exists(module, fs_id):
# Remove this record from the to_delete list. This means that
# the corresponding record have been found.
if module == self.module and fs_id in self.to_delete:
self.to_delete.remove(fs_id)
if self.noupdate and self.module_state != 'to activate':
return
# this record is already in the db:
db_value = self.fs2db.get(module, fs_id)
db_id = db_value['db_id']
db_model = db_value['model']
mdata_id = db_value['id']
old_values = db_value['values']
mdata = self.fs2db.get(module, fs_id)
# Check if record has not been deleted
if db_id is None:
if mdata.db_id is None:
return
if not old_values:
old_values = {}
else:
old_values = self.ModelData.load_values(old_values)
for key in old_values:
if isinstance(old_values[key], bytes):
# Fix for migration to unicode
old_values[key] = old_values[key].decode('utf-8')
if model != db_model:
raise ParsingError(
"wrong model '%s': %s.%s" % (model, module, fs_id))
record = self.fs2db.get_browserecord(module, Model.__name__, db_id)
# Re-create record if it was deleted
if not record:
with Transaction().set_context(module=module):
record, = Model.create([values])
# reset_browsercord
self.fs2db.reset_browsercord(
module, Model.__name__, [record.id])
record = self.fs2db.get_browserecord(
module, Model.__name__, record.id)
data = self.ModelData.search([
('fs_id', '=', fs_id),
('module', '=', module),
('model', '=', Model.__name__),
], limit=1)
self.ModelData.write(data, {
'db_id': record.id,
})
self.fs2db.get(module, fs_id)["db_id"] = record.id
to_update = {}
for key in values:
db_field = self._clean_value(key, record)
# if the fs value is the same as in the db, we ignore it
if db_field == values[key]:
continue
# we cannot update a field if it was changed by a user...
if key not in old_values:
expected_value = Model._defaults.get(key,
lambda *a: None)()
else:
expected_value = old_values[key]
# ... and we consider that there is an update if the
# expected value differs from the actual value, _and_
# if they are not false in a boolean context (ie None,
# False, {} or [])
if db_field != expected_value and (db_field or expected_value):
logger.warning(
"Field %s of %s@%s not updated (id: %s), because "
"it has changed since the last update",
key, record.id, model, fs_id)
continue
# so, the field in the fs and in the db are different,
# and no user changed the value in the db:
to_update[key] = values[key]
if self.grouped:
self.grouped_write[(module, model)].extend(
(record, to_update, old_values, values, fs_id, mdata_id))
self.grouped_write[model].extend([mdata, values])
else:
self.write_records(module, model,
record, to_update, old_values, values, fs_id, mdata_id)
self.write_records(model, mdata, values)
else:
if self.grouped:
self.grouped_creations[model][fs_id] = values
@@ -698,89 +556,38 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
def create_records(self, model, vlist, fs_ids):
Model = self.pool.get(model)
records = Model.create(vlist)
mdata_values = []
for record, values, fs_id in zip(records, vlist, fs_ids):
for key in values:
values[key] = self._clean_value(key, record)
mdata = self.ModelData(
fs_id=fs_id,
model=model,
module=self.module,
db_id=record.id,
field_names=tuple(values.keys()),
noupdate=self.noupdate,
)
self.fs2db.set(self.module, fs_id, mdata)
self.grouped_model_data.append(mdata)
mdata_values.append({
'fs_id': fs_id,
'model': model,
'module': self.module,
'db_id': record.id,
'values': self.ModelData.dump_values(values),
'fs_values': self.ModelData.dump_values(values),
'noupdate': self.noupdate,
})
models_data = self.ModelData.create(mdata_values)
for record, values, fs_id, mdata in zip(
records, vlist, fs_ids, models_data):
self.fs2db.set(self.module, fs_id, {
'db_id': record.id,
'model': model,
'id': mdata.id,
'values': self.ModelData.dump_values(values),
})
self.fs2db.reset_browsercord(self.module, model,
[r.id for r in records])
def write_records(self, module, model,
record, values, old_values, new_values, fs_id, mdata_id, *args):
args = (record, values, old_values, new_values, fs_id, mdata_id) + args
def write_records(self, model, *actions):
Model = self.pool.get(model)
actions = iter(args)
records = Model.browse([r.db_id for r in actions[::2]])
actions = iter(actions)
to_update = []
for record, values, _, _, _, _ in zip(*((actions,) * 6)):
if values:
for record, mdata, values in zip(records, actions, actions):
new_values = {}
for field, value in values.items():
new_value = self._clean_value(field, record)
if new_value != value:
new_values[field] = new_value
if new_values:
to_update += [[record], values]
# if there is values to update:
if values.keys() - set(mdata.field_names):
mdata.field_names = tuple(
set(mdata.field_names) | values.keys())
self.grouped_model_data.append(mdata)
if to_update:
# write the values in the db:
with Transaction().set_context(module=module):
Model.write(*to_update)
self.fs2db.reset_browsercord(
module, Model.__name__, sum(to_update[::2], []))
actions = iter(to_update)
for records, values in zip(actions, actions):
record, = records
# re-read it: this ensure that we store the real value
# in the model_data table:
record = self.fs2db.get_browserecord(
module, Model.__name__, record.id)
if not record:
record = Model(record.id)
for key in values:
values[key] = self._clean_value(key, record)
actions = iter(args)
for record, values, old_values, new_values, fs_id, mdata_id in zip(
*((actions,) * 6)):
temp_values = old_values.copy()
temp_values.update(values)
values = temp_values
fs_values = old_values.copy()
fs_values.update(new_values)
if old_values != values or values != fs_values:
self.grouped_model_data.extend(([self.ModelData(mdata_id)], {
'fs_id': fs_id,
'model': model,
'module': module,
'db_id': record.id,
'values': self.ModelData.dump_values(values),
'fs_values': self.ModelData.dump_values(fs_values),
'noupdate': self.noupdate,
}))
# reset_browsercord to keep cache memory low
self.fs2db.reset_browsercord(module, Model.__name__, args[::6])
Model.write(*to_update)
def post_import(pool, module, to_delete):
+13 -73
View File
@@ -1286,7 +1286,7 @@ class ModelData(
fields.fmany2one(
'module_ref', 'module', 'ir.module,name', "Module",
required=True, ondelete='CASCADE'),
ModelSQL, ModelView):
ModelSQL):
__name__ = 'ir.model.data'
fs_id = fields.Char('Identifier on File System', required=True,
help="The id of the record as known on the file system.")
@@ -1298,11 +1298,8 @@ class ModelData(
'required': ~Eval('noupdate', False),
},
help="The id of the record in the database.")
values = fields.Text('Values')
fs_values = fields.Text('Values on File System')
noupdate = fields.Boolean('No Update')
out_of_sync = fields.Function(fields.Boolean('Out of Sync'),
'get_out_of_sync', searcher='search_out_of_sync')
field_names = fields.MultiSelection('get_field_names', "Field Names")
_get_id_cache = Cache('ir_model_data.get_id', context=False)
_has_model_cache = Cache('ir_model_data.has_model', context=False)
@@ -1325,15 +1322,8 @@ class ModelData(
table,
(table.module, Index.Equality())),
})
cls._buttons.update({
'sync': {
'invisible': ~Eval('out_of_sync'),
'depends': ['out_of_sync'],
},
})
cls.__rpc__.update({
'sync': RPC(
readonly=False, instantiate=0, fresh_session=True),
'get_id': RPC(),
})
@classmethod
@@ -1345,24 +1335,16 @@ class ModelData(
# Migration from 5.0: remove required on db_id
table_h.not_null_action('db_id', action='remove')
@fields.depends('model_ref')
def get_field_names(self):
if not self.model_ref:
return []
return [(f.name, f.name) for f in self.model_ref.fields]
@staticmethod
def default_noupdate():
return False
def get_out_of_sync(self, name):
return self.values != self.fs_values and self.fs_values is not None
@classmethod
def search_out_of_sync(cls, name, clause):
table = cls.__table__()
name, operator, value = clause
Operator = fields.SQL_OPERATORS[operator]
query = table.select(table.id,
where=Operator(
(table.fs_values != table.values) & (table.fs_values != Null),
value))
return [('id', 'in', query)]
@classmethod
def create(cls, *args):
records = super(ModelData, cls).create(*args)
@@ -1414,16 +1396,15 @@ class ModelData(
**Model.__names__(record=record)),
gettext('ir.msg_base_config_record'))
else:
if not data.values or data.noupdate:
if not data.field_names or data.noupdate:
continue
xml_values = cls.load_values(data.values)
for key, val in values.items():
if key in xml_values and val != xml_values[key]:
for field in values:
if field in data.field_names:
raise AccessError(
gettext(
'ir.msg_write_xml_record',
**cls.__names__(
field=key, record=record)),
field=field, record=record)),
gettext('ir.msg_base_config_record'))
@classmethod
@@ -1474,47 +1455,6 @@ class ModelData(
def load_values(cls, values):
return dict(json.loads(values, object_hook=JSONDecoder()))
@classmethod
@ModelView.button
def sync(cls, records):
def settable(Model, fieldname):
try:
field = Model._fields[fieldname]
except KeyError:
return False
if isinstance(field, fields.Function) and not field.setter:
return False
return True
with Transaction().set_user(0):
pool = Pool()
to_write = []
models_to_write = defaultdict(list)
for data in records:
try:
Model = pool.get(data.model)
except KeyError:
continue
values = cls.load_values(data.values)
fs_values = cls.load_values(data.fs_values)
# values could be the same once loaded
# if they come from version < 3.2
if values != fs_values:
values = {f: v for f, v in fs_values.items()
if settable(Model, f)}
record = Model(data.db_id)
models_to_write[Model].extend(([record], values))
to_write.extend([[data], {
'values': cls.dump_values(fs_values),
'fs_values': cls.dump_values(fs_values),
}])
for Model, values_to_write in models_to_write.items():
Model.write(*values_to_write)
if to_write:
cls.write(*to_write)
class Log(ResourceAccessMixin, ModelSQL, ModelView):
"Log"
-53
View File
@@ -272,59 +272,6 @@ this repository contains the full copyright notices and license terms. -->
ref="act_model_button_click_form_relate_model_button"/>
</record>
<record model="ir.ui.view" id="model_data_view_list">
<field name="model">ir.model.data</field>
<field name="type">tree</field>
<field name="name">model_data_list</field>
</record>
<record model="ir.ui.view" id="model_data_view_form">
<field name="model">ir.model.data</field>
<field name="type">form</field>
<field name="name">model_data_form</field>
</record>
<record model="ir.action.act_window" id="act_model_data_form">
<field name="name">Data</field>
<field name="res_model">ir.model.data</field>
</record>
<record model="ir.action.act_window.view"
id="act_model_data_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="model_data_view_list"/>
<field name="act_window" ref="act_model_data_form"/>
</record>
<record model="ir.action.act_window.view"
id="act_model_data_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="model_data_view_form"/>
<field name="act_window" ref="act_model_data_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_model_data_form_domain_out_of_sync">
<field name="name">Out of Sync</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('out_of_sync', '=', True)]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_model_data_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_model_data_form_domain_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_model_data_form"/>
</record>
<menuitem
parent="menu_model_form"
action="act_model_data_form"
sequence="50"
id="menu_model_data_form"/>
<record model="ir.model.button" id="model_data_sync_button">
<field name="model">ir.model.data</field>
<field name="name">sync</field>
<field name="string">Sync</field>
</record>
<record model="ir.ui.view" id="model_log_view_form">
<field name="model">ir.model.log</field>
<field name="type">form</field>
@@ -1,27 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="module"/>
<field name="module"/>
<label name="model_ref"/>
<field name="model_ref"/>
<label name="fs_id"/>
<field name="fs_id"/>
<label name="db_id"/>
<field name="db_id"/>
<label name="noupdate"/>
<field name="noupdate"/>
<newline/>
<label name="out_of_sync"/>
<field name="out_of_sync"/>
<button name="sync" colspan="2"/>
<group id="values" colspan="2" yexpand="1" yfill="1" col="1">
<separator name="values"/>
<field name="values"/>
</group>
<group id="fs_values" colspan="2" yexpand="1" yfill="1" col="1">
<separator name="fs_values"/>
<field name="fs_values"/>
</group>
</form>
@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="module_ref"/>
<field name="model_ref" expand="1"/>
<field name="fs_id" expand="1"/>
<field name="db_id" optional="1"/>
<field name="noupdate"/>
<field name="out_of_sync"/>
<button name="sync"/>
</tree>
-5
View File
@@ -307,11 +307,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="button" ref="ir.cron_run_once_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button-res.group"
id="model_data_sync_button_group_admin">
<field name="button" ref="ir.model_data_sync_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button-res.group"
id="view_show_button_group_admin">
<field name="button" ref="ir.view_show_button"/>
+19 -19
View File
@@ -618,8 +618,8 @@ class ModelStorageTestCase(TestCase):
Model.check_xml_record([record], {'name': "Test"})
@with_transaction()
def test_check_xml_record_with_record_no_matching_values(self):
"Test check_xml_record with record and no matching values"
def test_check_xml_record_with_record_no_matching_fields(self):
"Test check_xml_record with record and no matching fields"
pool = Pool()
Model = pool.get('test.modelstorage')
ModelData = pool.get('ir.model.data')
@@ -629,15 +629,15 @@ class ModelStorageTestCase(TestCase):
'model': 'test.modelstorage',
'module': 'tests',
'db_id': record.id,
'values': ModelData.dump_values({}),
'field_names': None,
'noupdate': False,
}])
Model.check_xml_record([record], {'name': "Test"})
@with_transaction()
def test_check_xml_record_with_record_matching_values(self):
"Test check_xml_record with record and matching values"
def test_check_xml_record_with_record_matching_fields(self):
"Test check_xml_record with record and matching fields"
pool = Pool()
Model = pool.get('test.modelstorage')
ModelData = pool.get('ir.model.data')
@@ -647,7 +647,7 @@ class ModelStorageTestCase(TestCase):
'model': 'test.modelstorage',
'module': 'tests',
'db_id': record.id,
'values': ModelData.dump_values({'name': "Foo"}),
'field_names': ('name',),
'noupdate': False,
}])
@@ -655,8 +655,8 @@ class ModelStorageTestCase(TestCase):
Model.check_xml_record([record], {'name': "Bar"})
@with_transaction()
def test_check_xml_record_with_record_matching_values_noupdate(self):
"Test check_xml_record with record and matching values but noupdate"
def test_check_xml_record_with_record_matching_fields_noupdate(self):
"Test check_xml_record with record and matching fields but no update"
pool = Pool()
Model = pool.get('test.modelstorage')
ModelData = pool.get('ir.model.data')
@@ -666,14 +666,14 @@ class ModelStorageTestCase(TestCase):
'model': 'test.modelstorage',
'module': 'tests',
'db_id': record.id,
'values': ModelData.dump_values({'name': "Foo"}),
'field_names': ('name',),
'noupdate': True,
}])
Model.check_xml_record([record], {'name': "Bar"})
@with_transaction(user=0)
def test_check_xml_record_with_record_matching_values_root(self):
def test_check_xml_record_with_record_matching_fields_root(self):
"Test check_xml_record with record with matching values as root"
pool = Pool()
Model = pool.get('test.modelstorage')
@@ -684,14 +684,14 @@ class ModelStorageTestCase(TestCase):
'model': 'test.modelstorage',
'module': 'tests',
'db_id': record.id,
'values': ModelData.dump_values({'name': "Foo"}),
'field_names': ('name',),
'noupdate': False,
}])
Model.check_xml_record([record], {'name': "Bar"})
@with_transaction()
def test_check_xml_record_with_record_no_values(self):
def test_check_xml_record_with_record_no_fields(self):
"Test check_xml_record with record and no values"
pool = Pool()
Model = pool.get('test.modelstorage')
@@ -702,7 +702,7 @@ class ModelStorageTestCase(TestCase):
'model': 'test.modelstorage',
'module': 'tests',
'db_id': record.id,
'values': None,
'field_names': None,
'noupdate': False,
}])
@@ -710,8 +710,8 @@ class ModelStorageTestCase(TestCase):
Model.check_xml_record([record], None)
@with_transaction()
def test_check_xml_record_with_record_no_values_noupdate(self):
"Test check_xml_record with record and no values but noupdate"
def test_check_xml_record_with_record_no_fields_noupdate(self):
"Test check_xml_record with record and no fields but no update"
pool = Pool()
Model = pool.get('test.modelstorage')
ModelData = pool.get('ir.model.data')
@@ -721,7 +721,7 @@ class ModelStorageTestCase(TestCase):
'model': 'test.modelstorage',
'module': 'tests',
'db_id': record.id,
'values': None,
'field_names': None,
'noupdate': True,
}])
@@ -739,7 +739,7 @@ class ModelStorageTestCase(TestCase):
'model': 'test.modelstorage',
'module': 'tests',
'db_id': record.id,
'values': None,
'field_names': None,
'noupdate': True,
}])
@@ -749,7 +749,7 @@ class ModelStorageTestCase(TestCase):
@with_transaction(user=0)
def test_delete_model_data_without_noupdate(self):
"Test delete record from model data without noupdate"
"Test delete record from model data without no update"
pool = Pool()
Model = pool.get('test.modelstorage')
ModelData = pool.get('ir.model.data')
@@ -759,7 +759,7 @@ class ModelStorageTestCase(TestCase):
'model': 'test.modelstorage',
'module': 'tests',
'db_id': record.id,
'values': None,
'field_names': None,
'noupdate': False,
}])