This commit is contained in:
Cédric Krier committed 2008-02-28 17:31:37 +01:00
commit 9bfc4c6975
5 files changed
+400 -324

No files matched your search

+7 -271
View File
@@ -1,20 +1,10 @@
"model"
from trytond.osv import fields, OSV
from trytond.netsvc import Logger, LocalService, LOG_ERROR, LOG_INFO, LOG_WARNING, LOG_DEBUG
from trytond.osv.orm import ExceptORM
from trytond.tools import Cache
import time
# Custom exception:
class Unhandled_field(Exception):
"""
Raised when a field type is not supported by the update mechanism.
"""
pass
class Model(OSV):
"Model"
_name = 'ir.model'
@@ -183,271 +173,17 @@ class ModelData(OSV):
self.fs2values = None
def populate_fs2db(self, cursor, user):
"""Fetch all the db_id, model tuple in the table in one shot
(if necessary). This table is kept as a cache for the future
conversions between fs_id abd db_id"""
if not self.fs2db: self.fs2db = {}
if cursor.dbname not in self.fs2db:
module_data_ids = self.search(cursor, user,[])
self.fs2db[cursor.dbname] = dict([
((x.fs_id, x.module),
(x.db_id, x.model, x.id)) for x in \
self.browse(cursor, user, module_data_ids)])
def populate_fs2values(self, cursor, user):
"""Fetch all the table in one shot (if necessary). When a
record is imported ny import_record, the corresponding item in
fs2values is removed. When post_import is called, the items
remaining in fs2values are the one that were in the model_data
table but not in the fs, so they are removed."""
# Improvement: store the fiedls names and a hash of the values
# instead of all the values.
if not self.fs2values: self.fs2values = {}
if cursor.dbname not in self.fs2values:
module_data_ids = self.search(cursor, user,[])
self.fs2values[cursor.dbname] = dict([
((x.fs_id, x.module),
x.values) for x in \
self.browse(cursor, user, module_data_ids)])
def get_id(self, cursor, user, module, fs_id):
# Pre-Fetch all the data at once
self.populate_fs2db(cursor, user)
# return only the db_id
if (fs_id, module) in self.fs2db[cursor.dbname]:
return self.fs2db[cursor.dbname][(fs_id, module)][0]
else:
raise Exception("Reference to %s not found"%\
".".join([module,fs_id]))
@staticmethod
def _clean_value(key, browse_record, object_ref):
"""
Take a field name, a browse_record, and a reference to the
corresponding object. Return a raw value has it must look on the
db.
Return for an fs_id the corresponding db_id.
"""
ids = self.search(cursor, user,
[('module','=',module),('fs_is','=',fs_id)])
if not ids:
raise Exception("Reference to %s not found"% \
".".join([module,fs_id]))
return self.read(cursor, user, ids[0],['db_id'])['db_id']
# search the field type in the object or in a parent
if key in object_ref._columns:
field_type = object_ref._columns[key]._type
else:
field_type = object_ref._inherit_fields[key][2]._type
# handle the value regarding to the type
if field_type == 'many2one':
return browse_record[key] and browse_record[key].id or False
elif field_type in ['one2one', 'one2many', "many2many"]:
raise Unhandled_field()
else:
return browse_record[key]
def import_record(self, cursor, user, model, module, values, fs_id):
if not fs_id:
raise Exception('import_record : Argument fs_id is mandatory')
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('.')
self.populate_fs2db(cursor, user)
self.populate_fs2values(cursor, user)
object_ref = self.pool.get(model)
if (fs_id, module) in self.fs2db[cursor.dbname]:
# this record is already in the db:
db_id, db_model, mdata_id = \
self.fs2db[cursor.dbname][(fs_id, module)]
old_values = self.fs2values[cursor.dbname][(fs_id, module)]
if old_values == None:
old_values = {}
else:
old_values = eval(old_values)
# Check if values for this record has been modified in the
# db, if not it's ok to overwrite them.
if model != db_model:
raise Exception("This record try to overwrite"
"data with the wrong model: %s (module: %s)"% (fs_id, module))
#Re-create object if it was deleted
if not object_ref.search(cursor, user, [('id', '=', db_id)]):
db_id = object_ref.create(cursor, user, values,
context={'module': module})
data_id = self.search(cursor, user, [
('fs_id', '=', fs_id),
('module', '=', module),
], limit=1)[0]
self.write(cursor, user, data_id, {
'db_id': db_id,
})
self.fs2db[cursor.dbname][(fs_id, module)] = \
(db_id, db_model, mdata_id)
db_values = object_ref.browse(cursor, user, db_id)
to_update = {}
for key in values:
try:
db_field = ModelData._clean_value(key, db_values,
object_ref)
except Unhandled_field:
logger = Logger()
logger.notify_channel('init', LOG_WARNING,
'Field %s on %s : integrity not tested.'%(key, model))
to_update[key] = values[key]
continue
# if the fs value is the same has in the db, whe 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:
default_value = object_ref._defaults.get(
key, lambda *a:None)(cursor, user)
if db_field != default_value:
logger = Logger()
logger.notify_channel('init', LOG_WARNING,
"Field %s of %s@%s not updated (id: %s), because "\
"it has changed since the last update"% \
(key, db_id, model, fs_id))
continue
elif (old_values[key] and db_field) and \
old_values[key] != db_field:
logger = Logger()
logger.notify_channel('init', LOG_WARNING,
"Field %s of %s@%s not updated (id: %s), because "\
"it has changed since the last update."%\
(key, db_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 there is values to update:
if to_update:
# write the values in the db:
object_ref.write(cursor, user, db_id, to_update)
# re-read it: this ensure that we store the real value
# in the model_data table:
db_val = object_ref.browse(cursor, user, db_id)
for key in to_update:
try:
values[key] = ModelData._clean_value(
key, db_val, object_ref)
except Unhandled_field:
continue
if values != old_values:
self.write(cursor, user, mdata_id, {
'fs_id': fs_id,
'model': model,
'module': module,
'db_id': db_id,
'values': str(values),
'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
})
# Remove this record from the value list. This means that
# the corresponding record have been found.
self.fs2values[cursor.dbname][(fs_id, module)] = None
else:
# this record is new, create it in the db:
db_id = object_ref.create(cursor, user, values,
context={'module': module})
# re-read it: this ensure that we store the real value
# in the model_data table:
db_val = object_ref.browse(cursor, user, db_id)
for key in values:
try:
values[key] = ModelData._clean_value(key, db_val,
object_ref)
except Unhandled_field:
continue
mdata_id = self.create(cursor, user, {
'fs_id': fs_id,
'model': model,
'module': module,
'db_id': db_id,
'values': str(values),
})
# update fs2db:
self.fs2db[cursor.dbname][(fs_id, module)]= (db_id, model,
mdata_id)
self.fs2values[cursor.dbname][(fs_id, module)] = None
def post_import(self, cursor, user, modules):
# Test because of a wrong extra call. See todo at the end of
# load_module_graph in module.py
# Globaly this function is a bit dirty.
if not (self.fs2values and self.fs2values.get(cursor.dbname)):
return True
wf_service = LocalService("workflow")
mdata_unlink = []
for (fs_id, module) in self.fs2values[cursor.dbname]:
if self.fs2values[cursor.dbname][(fs_id, module)] == None:
continue
if module in modules:
(db_id, model, mdata_id) = self.fs2db[cursor.dbname]\
[(fs_id, module)]
if model == 'workflow.activity':
cursor.execute('SELECT res_type, db_id ' \
'FROM wkf_instance ' \
'WHERE id IN (' \
'SELECT instance FROM wkf_workitem ' \
'WHERE act_id = %s)', (db_id,))
wkf_todo.extend(cursor.fetchall())
cursor.execute("UPDATE wkf_transition " \
"SET condition = 'True', role_id = NULL, " \
"signal = NULL, act_to = act_from, " \
"act_from = %s " \
"WHERE act_to = %s", (db_id, db_id))
cursor.execute("DELETE FROM wkf_transition " \
"WHERE act_to = %s", (db_id,))
wf_service.trg_write(user, model, db_id, cursor)
logger = Logger()
logger.notify_channel('init', LOG_INFO,
'Deleting %s@%s' % (db_id, model))
try:
self.pool.get(model).unlink(cursor, user, db_id)
mdata_unlink.append(mdata_id)
except:
raise
logger.notify_channel('init', LOG_ERROR,
'Could not delete id: %d of model %s\n' \
'There should be some relation ' \
'that points to this resource\n' \
'You should manually fix this ' \
'and restart --update=module' % \
(db_id, model))
if mdata_unlink:
self.unlink(cursor, user, mdata_unlink)
del self.fs2values[cursor.dbname]
return True
ModelData()
+22 -7
View File
@@ -176,7 +176,7 @@ def init_module_wizards(cursor, module_name, wizard_list):
def load_module_graph(cursor, graph, lang=None):
if lang is None:
lang = ['en_US']
package_todo = []
modules_todo = []
for package in graph:
module = package.name
Logger().notify_channel('init', LOG_INFO, 'module:%s' % module)
@@ -197,6 +197,13 @@ def load_module_graph(cursor, graph, lang=None):
or (package_state in ('to install', 'to upgrade')):
init_module_objects(cursor, module, modules)
init_module_wizards(cursor, module, wizards)
#Instanciate a new parser for the package:
tryton_parser = tools.TrytondXmlHandler(
cursor=cursor,
pool=pooler.get_pool(cursor.dbname),
module=module,)
for filename in package.datas.get('xml', []):
mode = 'update'
if hasattr(package, 'init') or package_state=='to install':
@@ -213,8 +220,12 @@ def load_module_graph(cursor, graph, lang=None):
if new_query:
cursor.execute(new_query)
else:
tools.convert_xml_import(cursor, module,
tools.file_open(OPJ(module, filename)))
# Feed the parser with xml content:
tryton_parser.parse_xmlstream(
tools.file_open(OPJ(module, filename)))
modules_todo.append((module, tryton_parser.to_delete))
for filename in package.datas.get('translation', []):
lang2 = os.path.splitext(filename)[0]
if lang2 not in lang:
@@ -224,14 +235,18 @@ def load_module_graph(cursor, graph, lang=None):
translation_obj = pool.get('ir.translation')
translation_obj.translation_import(cursor, 0, lang2, module,
tools.file_open(OPJ(module, filename)))
package_todo.append(package.name)
cursor.execute("UPDATE ir_module_module SET state = 'installed' " \
"WHERE name = %s", (package.name,))
cursor.commit()
pool = pooler.get_pool(cursor.dbname)
# TODO : post_import is called even if there not init nor update
pool.get('ir.model.data').post_import(cursor, 0, package_todo)
# Vacuum :
while modules_todo:
(module, to_delete) = modules_todo.pop()
tools.post_import(cursor, module, to_delete)
cursor.commit()
def get_module_list():
+3
View File
@@ -1502,6 +1502,9 @@ class ORM(object):
user = user id
vals = dictionary of the form {'field_name': field_value, ...}
"""
vals = vals.copy()
self.pool.get('ir.model.access').check(cursor, user, self._name,
'create')
+2
View File
@@ -128,6 +128,8 @@ class User(OSV):
return res
def unlink(self, cursor, user, ids, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
if 1 in ids:
raise ExceptOSV('UserError',
'You can not remove the root user\n' \
+366 -46
View File
@@ -5,21 +5,16 @@ from trytond import pooler
from trytond.osv.fields import Integer
import csv
import os.path
from trytond.netsvc import Logger, LOG_ERROR, LOG_INFO, LocalService
from trytond.netsvc import Logger, LOG_ERROR, LOG_INFO, LocalService, LOG_WARNING
from trytond.config import CONFIG
from trytond.version import VERSION
import logging
import time
from xml import sax
CDATA_START = re.compile('^\s*\<\!\[cdata\[', re.IGNORECASE)
CDATA_END = re.compile('\]\]\>\s*$', re.IGNORECASE)
# TODO :
# Add some exception helper, and give a good diagnostic for the errors
from xml import sax
class DummyTagHandler:
"""Dubhandler implementing empty methods. Will be used when whe
want to ignore the xml content"""
@@ -133,9 +128,8 @@ class MenuitemTagHandler:
return self
else:
res = self.mh.pool.get('ir.model.data').import_record(
self.mh.cursor, self.mh.user,
'ir.ui.menu', self.mh.module, self.values, self.xml_id)
res = self.mh.import_record(
'ir.ui.menu', self.values, self.xml_id)
return None
def current_state(self):
@@ -264,18 +258,72 @@ class RecordTagHandler:
return self
elif name == "record":
res = self.mh.pool.get('ir.model.data').import_record(
self.mh.cursor, self.mh.user,
self.model._name, self.mh.module, self.values, self.xml_id)
res = self.mh.import_record(
self.model._name, self.values, self.xml_id)
return None
else:
raise Exception("Unexpected closing tag '%s'"% (name,))
def current_state(self):
return "Tag record for model %s with id: %s"% \
return "Last tag record seen: model %s with id %s."% \
(self.model and self.model._name or "?", self.xml_id)
# Custom exception:
class Unhandled_field(Exception):
"""
Raised when a field type is not supported by the update mechanism.
"""
pass
class Fs2bdAccessor:
"""
Used in TrytondXmlHandler.
Provide some helper function to ease cache access and management.
"""
def __init__(self, cursor, user, modeldata_obj):
self.fs2db = {}
self.fetched_modules = []
self.modeldata_obj = modeldata_obj
self.cursor = cursor
self.user = user
def get(self, module, fs_id):
if module not in self.fetched_modules:
self.fetch_new_module(module)
return self.fs2db[module].get(fs_id, 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 not module 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 fetch_new_module(self, module):
if module == "ir.ui.menu": raise
self.fs2db[module] = {}
module_data_ids = self.modeldata_obj.search(
self.cursor, self.user, [('module','=',module)]
)
for rec in self.modeldata_obj.browse(
self.cursor, self.user, module_data_ids):
self.fs2db[rec.module][rec.fs_id] = {
"db_id": rec.db_id, "model": rec.model,
"id": rec.id, "values": rec.values
}
self.fetched_modules.append(module)
class TrytondXmlHandler(sax.handler.ContentHandler):
def __init__(self, cursor, pool, module,):
@@ -285,7 +333,9 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
self.cursor = cursor
self.user = 0
self.module = module
self.modeldata_obj = pool.get('ir.model.data')
self.fs2db = Fs2bdAccessor(cursor, self.user, self.modeldata_obj)
self.to_delete = self.populate_to_delete()
# Tag handlders are used to delegate the processing
self.taghandlerlist = {
@@ -297,15 +347,32 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
# Managed tags are handled by the current class
self.managedtags= ["data", "tryton"]
# Connect to the sax api:
self.sax_parser = sax.make_parser()
# Tell the parser we are not interested in XML namespaces
self.sax_parser.setFeature(sax.handler.feature_namespaces, 0)
self.sax_parser.setContentHandler(self)
def get_id(self, xml_id):
module = self.module
if '.' in xml_id:
module, xml_id = xml_id.split('.')
def parse_xmlstream(self, stream):
"""
Take a byte stream has input and parse the xml content.
"""
return self.pool.get('ir.model.data').get_id(
self.cursor, self.user, module, xml_id)
source = sax.InputSource()
source.setByteStream(stream)
try:
self.sax_parser.parse(source)
except:
Logger().notify_channel(
"init", LOG_ERROR,
"Error while parsing xml file. Current state is:\n" +\
self.current_state()
)
raise
return self.to_delete
def startElement(self, name, attributes):
"""Rebind the current handler if necessary and call
@@ -347,35 +414,288 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
else:
return "In top level tag"
def convert_xml_import_sax(cursor, module, xmlstream):
def get_id(self, xml_id):
parser = sax.make_parser()
# Tell the parser we are not interested in XML namespaces
parser.setFeature(sax.handler.feature_namespaces, 0)
if '.' in xml_id:
module, xml_id = xml_id.split('.')
else:
module = self.module
handler = TrytondXmlHandler(
cursor=cursor,
pool=pooler.get_pool(cursor.dbname),
module=module,
)
if self.fs2db.get(module, xml_id) == None:
raise Exception("Reference to %s not found"% \
".".join([module,xml_id]))
return self.fs2db.get(module, xml_id)["db_id"]
parser.setContentHandler(handler)
source = sax.InputSource()
source.setByteStream(xmlstream)
try:
parser.parse(source)
except:
Logger().notify_channel(
"init", LOG_ERROR,
"Error while parsing xml file. Current state is:\n" +\
handler.current_state()
@staticmethod
def _clean_value(key, browse_record, object_ref):
"""
Take a field name, a browse_record, and a reference to the
corresponding object. Return a raw value has it must look on the
db.
"""
# search the field type in the object or in a parent
if key in object_ref._columns:
field_type = object_ref._columns[key]._type
else:
field_type = object_ref._inherit_fields[key][2]._type
# handle the value regarding to the type
if field_type == 'many2one':
return browse_record[key] and browse_record[key].id or False
elif field_type in ['one2one', 'one2many', "many2many"]:
raise Unhandled_field()
else:
return browse_record[key]
def populate_to_delete(self):
"""Create a list of all the records that whe should met in the update
process. The records that are not encountered are deleted from the
database in post_import."""
# Fetch the data in id descending order to avoid depedendcy
# problem when the corresponding recordds will be deleted:
module_data_ids = self.modeldata_obj.search(
self.cursor, self.user, [('module','=',self.module)],
order="id desc",
)
return [rec.fs_id for rec in self.modeldata_obj.browse(
self.cursor, self.user, module_data_ids)]
def import_record(self, model, values, fs_id):
cursor = self.cursor
module = self.module
user = self.user
if not fs_id:
raise Exception('import_record : Argument fs_id is mandatory')
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('.')
object_ref = self.pool.get(model)
if self.fs2db.get(module, fs_id):
# this record is already in the db:
# XXX maybe use only one call to get()
db_id, db_model, mdata_id, old_values = \
[self.fs2db.get(module, fs_id)[x] for x in \
["db_id","model","id","values"]
]
if old_values == None:
old_values = {}
else:
old_values = eval(old_values)
# Check if values for this record has been modified in the
# db, if not it's ok to overwrite them.
if model != db_model:
raise Exception("This record try to overwrite"
"data with the wrong model: %s (module: %s)"% (fs_id, module))
#Re-create object if it was deleted
if not object_ref.search(cursor, user, [('id', '=', db_id)]):
db_id = object_ref.create(cursor, user, values,
context={'module': module})
data_id = self.modeldata_obj.search(cursor, user, [
('fs_id', '=', fs_id),
('module', '=', module),
], limit=1)[0]
self.modeldata_obj.write(cursor, user, data_id, {
'db_id': db_id,
})
self.fs2db.get(module, fs_id)["db_id"] = db_id
db_val = object_ref.browse(cursor, user, db_id)
to_update = {}
for key in values:
try:
db_field = self._clean_value(key, db_val, object_ref)
except Unhandled_field:
logger = Logger()
logger.notify_channel('init', LOG_WARNING,
'Field %s on %s : integrity not tested.'%(key, model))
to_update[key] = values[key]
continue
# if the fs value is the same has in the db, whe 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:
default_value = object_ref._defaults.get(
key, lambda *a:None)(cursor, user)
if db_field != default_value:
logger = Logger()
logger.notify_channel('init', LOG_WARNING,
"Field %s of %s@%s not updated (id: %s), because "\
"it has changed since the last update"% \
(key, db_id, model, fs_id))
continue
elif (old_values[key] and db_field) and \
old_values[key] != db_field:
logger = Logger()
logger.notify_channel('init', LOG_WARNING,
"Field %s of %s@%s not updated (id: %s), because "\
"it has changed since the last update."%\
(key, db_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 there is values to update:
if to_update:
# write the values in the db:
object_ref.write(cursor, user, db_id, to_update)
# re-read it: this ensure that we store the real value
# in the model_data table:
db_val = object_ref.browse(cursor, user, db_id)
for key in to_update:
try:
values[key] = self._clean_value(
key, db_val, object_ref)
except Unhandled_field:
continue
if values != old_values:
self.modeldata_obj.write(cursor, user, mdata_id, {
'fs_id': fs_id,
'model': model,
'module': module,
'db_id': db_id,
'values': str(values),
'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
})
# Remove this record from the to_delete list. This means that
# the corresponding record have been found.
if module == self.module:
self.to_delete.remove(fs_id)
else:
# this record is new, create it in the db:
db_id = object_ref.create(cursor, user, values,
context={'module': module})
# re-read it: this ensure that we store the real value
# in the model_data table:
db_val = object_ref.browse(cursor, user, db_id)
for key in values:
try:
values[key] = self._clean_value(key, db_val,
object_ref)
except Unhandled_field:
continue
mdata_id = self.modeldata_obj.create(cursor, user, {
'fs_id': fs_id,
'model': model,
'module': module,
'db_id': db_id,
'values': str(values),
})
# update fs2db:
self.fs2db.set(module, fs_id, {
"db_id": db_id, "model": model,
"id": mdata_id, "values": str(values)})
def post_import(cursor, module, to_delete):
"""
Remove the records that are given in to_delete.
"""
user = 0
wf_service = LocalService("workflow")
mdata_unlink = []
pool = pooler.get_pool(cursor.dbname)
modeldata_obj = pool.get("ir.model.data")
mdata_ids = modeldata_obj.search(
cursor, user, [('fs_id','in',to_delete)],
order="id desc",
)
raise
for mrec in modeldata_obj.browse(cursor, user, mdata_ids):
mdata_id, model,db_id = mrec.id, mrec.model, mrec.db_id
# Whe skip transitions, they will be deleted with the
# corresponding activity:
if model == 'workflow.transition':
continue
if model == 'workflow.activity':
wkf_todo = []
# search for records that are in the state/activity that
# we want to delete...
cursor.execute('SELECT res_type, db_id ' \
'FROM wkf_instance ' \
'WHERE id IN (' \
'SELECT instance FROM wkf_workitem ' \
'WHERE act_id = %d)', (db_id,))
#... connect the transitions backward...
wkf_todo.extend(cursor.fetchall())
cursor.execute("UPDATE wkf_transition " \
"SET condition = 'True', role_id = NULL, " \
"signal = NULL, act_to = act_from, " \
"act_from = %d " \
"WHERE act_to = %d", (db_id, db_id))
# ... and force the record to follow them:
for wkf_model,wkf_model_id in wkf_todo:
wf_service = netsvc.LocalService("workflow")
wf_service.trg_write(uid, model, id, cr)
# Collect the ids of these transition in model_data
cursor.execute(
"SELECT md.id FROM ir_model_data md " \
"JOIN wkf_transition t ON "\
"(md.model='workflow.transition' and md.db_id=t.id)" \
"WHERE t.act_to = %d", (db_id,))
mdata_unlink.extend([x[0] for x in cr.fetchall()])
# And finally delete the transitions
cursor.execute("DELETE FROM wkf_transition " \
"WHERE act_to = %d", (db_id,))
wf_service.trg_write(user, model, db_id, cursor)
logger = Logger()
logger.notify_channel('init', LOG_INFO,
'Deleting %s@%s' % (db_id, model))
try:
# Deletion of the record
pool.get(model).unlink(cursor, user, db_id)
mdata_unlink.append(mdata_id)
except:
raise
logger.notify_channel('init', LOG_ERROR,
'Could not delete id: %d of model %s\n' \
'There should be some relation ' \
'that points to this resource\n' \
'You should manually fix this ' \
'and restart --update=module' % \
(db_id, model))
# Clean model_data:
if mdata_unlink:
modeldata_obj.unlink(cursor, user, mdata_unlink)
return True
# use convert_xml_import_sax or convert_xml_import_dom
convert_xml_import = convert_xml_import_sax