_auto_init refectoring:
* cleaner code. * added some constraint: avoid to do destructive migrations. * added not null and index update.
This commit is contained in:
1 parent
0734bff205
commit
3b32e80b27
4 files changed
+367
-373
No files matched your search
@@ -37,6 +37,8 @@ class ModelField(OSV):
|
||||
group_name = fields.Char('Group Name', size=None)
|
||||
view_load = fields.Boolean('View Auto-Load')
|
||||
help = fields.Text('Help', translate=True)
|
||||
module = fields.Char('Module', size=None,
|
||||
help="Module in which this field is defined")
|
||||
|
||||
def default_relate(self, cursor, user, context=None):
|
||||
return 0
|
||||
|
||||
@@ -83,6 +83,8 @@
|
||||
<field name="field_description" colspan="3"/>
|
||||
<label name="help"/>
|
||||
<field name="help" colspan="3"/>
|
||||
<label name="module"/>
|
||||
<field name="module"/>
|
||||
</form>
|
||||
]]>
|
||||
</field>
|
||||
@@ -98,6 +100,7 @@
|
||||
<field name="ttype" select="2"/>
|
||||
<field name="relation" select="2"/>
|
||||
<field name="field_description" select="2"/>
|
||||
<field name="module"/>
|
||||
</tree>
|
||||
]]>
|
||||
</field>
|
||||
|
||||
+105
-373
@@ -9,6 +9,7 @@ import traceback
|
||||
import datetime
|
||||
from lxml import etree
|
||||
import copy
|
||||
from trytond.sql_db import table_handler
|
||||
|
||||
ID_MAX = 1000
|
||||
|
||||
@@ -265,13 +266,13 @@ class ORM(object):
|
||||
cursor.execute('SELECT f.id AS id, f.name AS name, ' \
|
||||
'f.field_description AS field_description, ' \
|
||||
'f.ttype AS ttype, f.relation AS relation, ' \
|
||||
'f.group_name AS group_name, f.view_load AS view_load ' \
|
||||
'f.group_name AS group_name, f.view_load AS view_load, ' \
|
||||
'f.module as module '\
|
||||
'FROM ir_model_field AS f, ir_model AS m ' \
|
||||
'WHERE f.model = m.id ' \
|
||||
'AND m.model = %s ' \
|
||||
'AND f.name in ' \
|
||||
'(' + ','.join(['%s' for x in self._columns]) + ')',
|
||||
(self._name,) + tuple(self._columns))
|
||||
'AND f.module = %s ',
|
||||
(self._name, module_name))
|
||||
columns = {}
|
||||
for column in cursor.dictfetchall():
|
||||
columns[column['name']] = column
|
||||
@@ -299,12 +300,12 @@ class ORM(object):
|
||||
if k not in columns:
|
||||
cursor.execute("INSERT INTO ir_model_field " \
|
||||
"(model, name, field_description, ttype, " \
|
||||
"relation, group_name, view_load, help) " \
|
||||
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
|
||||
"relation, group_name, view_load, help, module) " \
|
||||
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
|
||||
(model_id, k, field.string, field._type,
|
||||
field._obj or '', field.group_name or '',
|
||||
(field.view_load and 'True') or 'False',
|
||||
field.help))
|
||||
field.help, module_name))
|
||||
elif columns[k]['field_description'] != field.string \
|
||||
or columns[k]['ttype'] != field._type \
|
||||
or columns[k]['relation'] != (field._obj or '') \
|
||||
@@ -361,6 +362,13 @@ class ORM(object):
|
||||
'VALUES (%s, %s, %s, %s, %s, %s)',
|
||||
(trans_name, 'en_US', 'selection', val, '',
|
||||
module_name))
|
||||
# Clean ir_model_field from field that are no more existing.
|
||||
for k in columns:
|
||||
if columns[k]['module'] == module_name and k not in self._columns:
|
||||
# XXX This delete field even when it is defined later in the module
|
||||
cursor.execute('DELETE FROM ir_model_field '\
|
||||
'WHERE id = %s',
|
||||
(columns[k]['id'],))
|
||||
|
||||
def auto_init(self, cursor, module_name):
|
||||
self.init(cursor, module_name)
|
||||
@@ -383,372 +391,92 @@ class ORM(object):
|
||||
'VALUES (%s, %s, %s, %s, %s, %s)',
|
||||
(self._name, 'en_US', 'error', error, '', module_name))
|
||||
|
||||
|
||||
|
||||
def _auto_init(self, cursor, module_name):
|
||||
logger = Logger()
|
||||
create = False
|
||||
|
||||
self._field_create(cursor, module_name)
|
||||
if self._auto and not self.table_query():
|
||||
cursor.execute("SELECT relname FROM pg_class " \
|
||||
"WHERE relkind in ('r', 'v') AND relname = %s",
|
||||
(self._table,))
|
||||
if not cursor.rowcount:
|
||||
cursor.execute("CREATE TABLE \"%s\" " \
|
||||
"(id SERIAL NOT NULL, " \
|
||||
"PRIMARY KEY(id))" % self._table)
|
||||
create = True
|
||||
if self._log_access:
|
||||
logs = {
|
||||
'create_uid': 'INTEGER REFERENCES res_user ' \
|
||||
'ON DELETE SET NULL',
|
||||
'create_date': 'TIMESTAMP',
|
||||
'write_uid': 'INTEGER REFERENCES res_user ' \
|
||||
'ON DELETE SET NULL',
|
||||
'write_date': 'TIMESTAMP'
|
||||
}
|
||||
for k in logs:
|
||||
cursor.execute("SELECT c.relname " \
|
||||
"FROM pg_class c, pg_attribute a " \
|
||||
"WHERE c.relname = %s " \
|
||||
"AND a.attname = %s " \
|
||||
"AND c.oid = a.attrelid",
|
||||
(self._table, k))
|
||||
if not cursor.rowcount:
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ADD COLUMN \"%s\" %s" %
|
||||
(self._table, k, logs[k]))
|
||||
if not self._auto or self.table_query():
|
||||
# No db table for this object.
|
||||
return
|
||||
|
||||
# iterate on the database columns to drop the NOT NULL constraints
|
||||
# of fields which were required but have been removed
|
||||
cursor.execute(
|
||||
"SELECT a.attname, a.attnotnull "\
|
||||
"FROM pg_class c, pg_attribute a "\
|
||||
"WHERE c.oid=a.attrelid AND c.relname='%s'" % self._table)
|
||||
db_columns = cursor.dictfetchall()
|
||||
for column in db_columns:
|
||||
if column['attname'] not in (
|
||||
'id',
|
||||
'oid',
|
||||
'tableoid',
|
||||
'ctid',
|
||||
'xmin',
|
||||
'xmax',
|
||||
'cmin',
|
||||
'cmax',
|
||||
):
|
||||
if column['attnotnull'] \
|
||||
and (column['attname'] not in self._columns):
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ALTER COLUMN \"%s\" DROP NOT NULL" % \
|
||||
(self._table, column['attname']))
|
||||
table = table_handler(cursor, self._table, self._name, module_name)
|
||||
if self._log_access:
|
||||
logs = (
|
||||
('create_date', 'timestamp', 'TIMESTAMP'),
|
||||
('write_date', 'timestamp', 'TIMESTAMP'),
|
||||
('create_uid', 'int4',
|
||||
'INTEGER REFERENCES res_user ON DELETE SET NULL'),
|
||||
('write_uid', 'int4',
|
||||
'INTEGER REFERENCES res_user ON DELETE SET NULL'),
|
||||
)
|
||||
for log in logs:
|
||||
table.add_raw_column(log[0], (log[1], log[2]), migrate=False)
|
||||
for field_name, field in self._columns.iteritems():
|
||||
default_fun = None
|
||||
if field_name in (
|
||||
'id',
|
||||
'write_uid',
|
||||
'write_date',
|
||||
'create_uid',
|
||||
'create_date',
|
||||
):
|
||||
continue
|
||||
|
||||
# iterate on the "object columns"
|
||||
for k in self._columns:
|
||||
if k in (
|
||||
'id',
|
||||
'write_uid',
|
||||
'write_date',
|
||||
'create_uid',
|
||||
'create_date',
|
||||
):
|
||||
continue
|
||||
if field._classic_write:
|
||||
if isinstance(field, (fields.Integer, fields.Float)):
|
||||
default = 0 #XXX why ?
|
||||
|
||||
field = self._columns[k]
|
||||
if isinstance(field, fields.One2Many):
|
||||
cursor.execute("SELECT relname FROM pg_class " \
|
||||
"WHERE relkind = 'r' AND relname = %s",
|
||||
(field._obj,))
|
||||
if cursor.fetchone():
|
||||
cursor.execute("SELECT count(*) as c " \
|
||||
"FROM pg_class c, pg_attribute a " \
|
||||
"WHERE c.relname = %s " \
|
||||
"AND a.attname = %s " \
|
||||
"AND c.oid = a.attrelid",
|
||||
(field._obj, field._field))
|
||||
(res,) = cursor.fetchone()
|
||||
if not res:
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ADD FOREIGN KEY (%s) " \
|
||||
"REFERENCES \"%s\" ON DELETE SET NULL" % \
|
||||
(self._obj, field._field, field._table))
|
||||
elif isinstance(field, fields.Many2Many):
|
||||
cursor.execute("SELECT relname FROM pg_class " \
|
||||
"WHERE relkind in ('r','v') AND relname=%s",
|
||||
(field._rel,))
|
||||
if not cursor.dictfetchall():
|
||||
# for some fields in ir are linked to res objects
|
||||
# that are not yet in the pooler
|
||||
if field._obj in ('res.user', 'res.group'):
|
||||
ref = field._obj.replace('.','_')
|
||||
else:
|
||||
ref = self.pool.get(field._obj)._table
|
||||
cursor.execute("CREATE TABLE \"%s\" " \
|
||||
"(\"%s\" INTEGER NOT NULL REFERENCES \"%s\" " \
|
||||
"ON DELETE CASCADE, " \
|
||||
"\"%s\" INTEGER NOT NULL REFERENCES \"%s\" " \
|
||||
"ON DELETE CASCADE)" % \
|
||||
(field._rel, field._id1, self._table,
|
||||
field._id2, ref))
|
||||
cursor.execute("CREATE INDEX \"%s_%s_index\" " \
|
||||
"ON \"%s\" (\"%s\")" % \
|
||||
(field._rel, field._id1, field._rel,
|
||||
field._id1))
|
||||
cursor.execute("CREATE INDEX \"%s_%s_index\" " \
|
||||
"ON \"%s\" (\"%s\")" % \
|
||||
(field._rel, field._id2, field._rel,
|
||||
field._id2))
|
||||
else:
|
||||
cursor.execute("SELECT c.relname, a.attname, a.attlen, " \
|
||||
"a.atttypmod, a.attnotnull, a.atthasdef, " \
|
||||
"t.typname, " \
|
||||
"CASE WHEN a.attlen = -1 " \
|
||||
"THEN a.atttypmod-4 " \
|
||||
"ELSE a.attlen END as size " \
|
||||
"FROM pg_class c, pg_attribute a, pg_type t " \
|
||||
"WHERE c.relname = %s " \
|
||||
"AND a.attname = %s " \
|
||||
"AND c.oid = a.attrelid " \
|
||||
"AND a.atttypid = t.oid",
|
||||
(self._table, k))
|
||||
res = cursor.dictfetchall()
|
||||
if not res:
|
||||
if not isinstance(field, fields.Function):
|
||||
# add the missing field
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ADD COLUMN \"%s\" %s" % \
|
||||
(self._table, k, field.sql_type()[1]))
|
||||
if isinstance(field, (fields.Integer, fields.Float)):
|
||||
cursor.execute('ALTER TABLE "%s" ' \
|
||||
'ALTER COLUMN "%s" SET DEFAULT 0' % \
|
||||
(self._table, k))
|
||||
# initialize it
|
||||
if not create and k in self._defaults:
|
||||
default = self._defaults[k](cursor, 1, {})
|
||||
if not default:
|
||||
cursor.execute("UPDATE \"%s\" " \
|
||||
"SET \"%s\" = NULL" % \
|
||||
(self._table, k))
|
||||
else:
|
||||
if isinstance(field, fields.Many2One) \
|
||||
and isinstance(default, (list, tuple)):
|
||||
default = default[0]
|
||||
cursor.execute("UPDATE \"%s\" " \
|
||||
"SET \"%s\" = '%s'" % \
|
||||
(self._table, k, default))
|
||||
# and add constraints if needed
|
||||
if isinstance(field, fields.Many2One):
|
||||
# res.user and res.group are not present when ir initialize
|
||||
if field._obj == 'res.user':
|
||||
ref = 'res_user'
|
||||
elif field._obj == 'res.group':
|
||||
ref = 'res_group'
|
||||
else:
|
||||
ref = self.pool.get(field._obj)._table
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ADD FOREIGN KEY (\"%s\") " \
|
||||
"REFERENCES \"%s\" " \
|
||||
"ON DELETE %s" % \
|
||||
(self._table, k, ref,
|
||||
field.ondelete))
|
||||
if field.select:
|
||||
cursor.execute("CREATE INDEX \"%s_%s_index\" " \
|
||||
"ON \"%s\" (\"%s\")" % \
|
||||
(self._table, k, self._table, k))
|
||||
if field.required:
|
||||
cursor.execute('SELECT id FROM "%s" ' \
|
||||
'WHERE "%s" IS NULL' % \
|
||||
(self._table, k))
|
||||
if not cursor.rowcount:
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ALTER COLUMN \"%s\" " \
|
||||
"SET NOT NULL" % \
|
||||
(self._table, k))
|
||||
else:
|
||||
logger.notify_channel('init',
|
||||
LOG_WARNING,
|
||||
'Unable to set column %s ' \
|
||||
'of table %s not null !\n'\
|
||||
'Try to re-run: ' \
|
||||
'trytond.py --update=module\n' \
|
||||
'If it doesn\'t work, update records and execute manually:\n' \
|
||||
'ALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % \
|
||||
(k, self._table, self._table, k))
|
||||
elif len(res)==1:
|
||||
f_pg_def = res[0]
|
||||
f_pg_type = f_pg_def['typname']
|
||||
f_pg_size = f_pg_def['size']
|
||||
f_pg_notnull = f_pg_def['attnotnull']
|
||||
if isinstance(field, fields.Function):
|
||||
logger.notify_channel('init', LOG_WARNING,
|
||||
'column %s (%s) in table %s was converted '\
|
||||
'to a function !\n' \
|
||||
'You should remove this column from your database.' % \
|
||||
(k, field.string, self._table))
|
||||
f_obj_type = None
|
||||
else:
|
||||
f_obj_type = field.sql_type() \
|
||||
and field.sql_type()[0]
|
||||
if f_obj_type:
|
||||
if f_pg_type != f_obj_type:
|
||||
logger.notify_channel('init',
|
||||
LOG_WARNING,
|
||||
"column '%s' in table '%s' must " \
|
||||
"change type %s -> %s!" % \
|
||||
(k, self._table, f_pg_type, f_obj_type,))
|
||||
if f_pg_type == 'varchar' \
|
||||
and field._type == 'char' \
|
||||
and (f_pg_size != field.size \
|
||||
and not (f_pg_size == -5 and field.size is None)):
|
||||
# columns with the name 'type' cannot be changed
|
||||
# for an unknown reason?!
|
||||
if k != 'type':
|
||||
if field.size and f_pg_size > field.size:
|
||||
logger.notify_channel('init',
|
||||
LOG_WARNING,
|
||||
"column '%s' in table '%s' " \
|
||||
"has changed size (DB = %d, def = %d), strings will be truncated !" % \
|
||||
(k, self._table, f_pg_size, field.size))
|
||||
#TODO: check si y a des donnees qui vont poser probleme (select char_length())
|
||||
#TODO: issue a log message even if f_pg_size < field.size
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"RENAME COLUMN \"%s\" " \
|
||||
"TO temp_change_size" % \
|
||||
(self._table,k))
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ADD COLUMN \"%s\" %s" % \
|
||||
(self._table, k, field.sql_type()[1]))
|
||||
cursor.execute("UPDATE \"%s\" " \
|
||||
"SET \"%s\" = temp_change_size::%s" % \
|
||||
(self._table, k, field.sql_type()[1]))
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"DROP COLUMN temp_change_size" % \
|
||||
(self._table,))
|
||||
# if the field is required
|
||||
# and hasn't got a NOT NULL constraint
|
||||
if field.required and f_pg_notnull == 0:
|
||||
# set the field to the default value if any
|
||||
if self._defaults.has_key(k):
|
||||
default = self._defaults[k](cursor,
|
||||
1, {})
|
||||
if default:
|
||||
if isinstance(field, fields.Many2One) \
|
||||
and isinstance(default, (list, tuple)):
|
||||
default = default[0]
|
||||
cursor.execute("UPDATE \"%s\" " \
|
||||
"SET \"%s\" = '%s' WHERE %s is NULL" % \
|
||||
(self._table, k, default, k))
|
||||
cursor.execute('SELECT "%s" FROM "%s" ' \
|
||||
'WHERE "%s" IS NULL' % \
|
||||
(k, self._table, k))
|
||||
if not cursor.rowcount:
|
||||
# add the NOT NULL constraint
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ALTER COLUMN \"%s\" SET NOT NULL" % \
|
||||
(self._table, k))
|
||||
else:
|
||||
logger.notify_channel('init',
|
||||
LOG_WARNING,
|
||||
'unable to set ' \
|
||||
'a NOT NULL constraint on column %s of the %s table !\n' \
|
||||
'If you want to have it, you should update the records and execute manually:\n'\
|
||||
'ALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % \
|
||||
(k, self._table, self._table, k))
|
||||
elif not field.required and f_pg_notnull == 1:
|
||||
cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ALTER COLUMN \"%s\" DROP NOT NULL" % \
|
||||
(self._table, k))
|
||||
cursor.execute("SELECT indexname FROM pg_indexes " \
|
||||
"WHERE indexname = '%s_%s_index' AND tablename = '%s'" % \
|
||||
(self._table, k, self._table))
|
||||
res = cursor.dictfetchall()
|
||||
if not res and field.select:
|
||||
cursor.execute("CREATE INDEX \"%s_%s_index\" " \
|
||||
"ON \"%s\" (\"%s\")" % \
|
||||
(self._table, k, self._table, k))
|
||||
if res and not field.select:
|
||||
cursor.execute("DROP INDEX \"%s_%s_index\"" % \
|
||||
(self._table, k))
|
||||
if isinstance(field, fields.Many2One):
|
||||
# for some fields in ir are linked to res objects
|
||||
# that are not yet in the pooler
|
||||
if field._obj in ('res.user', 'res.group'):
|
||||
ref = field._obj.replace('.','_')
|
||||
else:
|
||||
ref = self.pool.get(field._obj)._table
|
||||
cursor.execute('SELECT confdeltype, conname ' \
|
||||
'FROM pg_constraint as con, ' \
|
||||
'pg_class as cl1, pg_class as cl2, ' \
|
||||
'pg_attribute as att1, pg_attribute as att2 ' \
|
||||
'WHERE con.conrelid = cl1.oid ' \
|
||||
'AND cl1.relname = %s ' \
|
||||
'AND con.confrelid = cl2.oid ' \
|
||||
'AND cl2.relname = %s ' \
|
||||
'AND array_lower(con.conkey, 1) = 1 ' \
|
||||
'AND con.conkey[1] = att1.attnum ' \
|
||||
'AND att1.attrelid = cl1.oid ' \
|
||||
'AND att1.attname = %s ' \
|
||||
'AND array_lower(con.confkey, 1) = 1 ' \
|
||||
'AND con.confkey[1] = att2.attnum ' \
|
||||
'AND att2.attrelid = cl2.oid ' \
|
||||
'AND att2.attname = %s ' \
|
||||
'AND con.contype = \'f\'',
|
||||
(self._table, ref, k, 'id'))
|
||||
res = cursor.dictfetchall()
|
||||
if res:
|
||||
confdeltype = {
|
||||
'RESTRICT': 'r',
|
||||
'NO ACTION': 'a',
|
||||
'CASCADE': 'c',
|
||||
'SET NULL': 'n',
|
||||
'SET DEFAULT': 'd',
|
||||
}
|
||||
if res[0]['confdeltype'] != \
|
||||
confdeltype.get(
|
||||
field.ondelete.upper(),
|
||||
'a'):
|
||||
cursor.execute('ALTER TABLE "' + \
|
||||
self._table + '" ' \
|
||||
'DROP CONSTRAINT "' + \
|
||||
res[0]['conname'] + '"')
|
||||
cursor.execute('ALTER TABLE "' + \
|
||||
self._table + '" ' \
|
||||
'ADD FOREIGN KEY ' \
|
||||
'("' + k + '") ' \
|
||||
'REFERENCES "' + ref + \
|
||||
'" ON DELETE ' + field.ondelete)
|
||||
if field_name in self._defaults:
|
||||
default_fun = self._defaults[field_name]
|
||||
|
||||
def unpack_wrapper(fun):
|
||||
def unpack_result(*a):
|
||||
try: # XXX ugly hack
|
||||
result = fun(*a)
|
||||
except:
|
||||
return None
|
||||
clean_results = self.__clean_defaults(
|
||||
{field_name: result})
|
||||
return clean_results[field_name]
|
||||
return unpack_result
|
||||
default_fun = unpack_wrapper(default_fun)
|
||||
|
||||
print field.sql_type()
|
||||
|
||||
table.add_raw_column(
|
||||
field_name, field.sql_type(), default_fun, field.size)
|
||||
|
||||
if isinstance(field, fields.Many2One):
|
||||
if field._obj in ('res.user', 'res.group'):
|
||||
ref = field._obj.replace('.','_')
|
||||
else:
|
||||
# TODO add error message
|
||||
logger.notify_channel('init', LOG_ERROR, '')
|
||||
else:
|
||||
cursor.execute("SELECT relname FROM pg_class " \
|
||||
"WHERE relkind in ('r', 'v') AND relname = %s",
|
||||
(self._table,))
|
||||
create = not bool(cursor.fetchone())
|
||||
ref = self.pool.get(field._obj)._table
|
||||
table.add_fk(field_name, ref, field.ondelete)
|
||||
|
||||
for (key, con, msg) in self._sql_constraints:
|
||||
cursor.execute("SELECT conname FROM pg_constraint " \
|
||||
"WHERE conname = %s", ((self._table + '_' + key),))
|
||||
if not cursor.dictfetchall():
|
||||
try:
|
||||
cursor.execute('ALTER TABLE \"%s\" ' \
|
||||
'ADD CONSTRAINT \"%s_%s\" %s' % \
|
||||
(self._table, self._table, key, con,))
|
||||
except:
|
||||
logger.notify_channel('init', LOG_WARNING,
|
||||
'unable to add \'%s\' constraint on table %s !\n' \
|
||||
'If you want to have it, you should update the records and execute manually:\n'\
|
||||
'ALTER table %s ADD CONSTRAINT %s_%s %s' % \
|
||||
(con, self._table, self._table, self._table,key, con,))
|
||||
if not isinstance(field, fields.Many2One):
|
||||
# Postgres does not allow index on fk
|
||||
table.index_action(
|
||||
field_name, action=field.select and 'add' or 'remove')
|
||||
table.not_null_action(
|
||||
field_name, action=field.required and 'add' or 'remove')
|
||||
|
||||
if create:
|
||||
if hasattr(self, "_sql"):
|
||||
for line in self._sql.split(';'):
|
||||
line2 = line.replace('\n', '').strip()
|
||||
if line2:
|
||||
cursor.execute(line2)
|
||||
elif isinstance(field, fields.Many2Many):
|
||||
if field._obj in ('res.user', 'res.group'):
|
||||
ref = field._obj.replace('.','_')
|
||||
else:
|
||||
ref = self.pool.get(field._obj)._table
|
||||
table.add_m2m(field_name, ref, field._rel, field._id1, field._id2)
|
||||
|
||||
for k in self._columns:
|
||||
field = self._columns[k]
|
||||
elif not isinstance(field, (fields.One2Many, fields.Function)):
|
||||
raise Exception('Unknow field type !')
|
||||
|
||||
|
||||
if "type has changed":
|
||||
pass #try_to_migrate
|
||||
|
||||
|
||||
for field_name, field in self._columns.iteritems():
|
||||
if isinstance(field, fields.Many2One) \
|
||||
and field._obj == self._name \
|
||||
and field.left and field.right:
|
||||
@@ -758,7 +486,12 @@ class ORM(object):
|
||||
(self._table, field.left, field.right,
|
||||
field.left, field.right))
|
||||
if cursor.rowcount:
|
||||
self._rebuild_tree(cursor, 0, k, False, 0)
|
||||
self._rebuild_tree(cursor, 0, field, False, 0)
|
||||
|
||||
|
||||
for ident, constraint, msg in self._sql_constraints:
|
||||
table.add_constraint( ident, constraint)
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self._rpc_allowed = [
|
||||
@@ -1732,6 +1465,8 @@ class ORM(object):
|
||||
vals[field].append(('create', vals2))
|
||||
elif fld_def._type in ('many2many',):
|
||||
vals[field] = [('set', defaults[field])]
|
||||
elif fld_def._type in ('boolean',):
|
||||
vals[field] = bool(defaults[field])
|
||||
else:
|
||||
vals[field] = defaults[field]
|
||||
return vals
|
||||
@@ -2154,11 +1889,11 @@ class ORM(object):
|
||||
while test:
|
||||
if view_id:
|
||||
where = (model and (" and model='%s'" % (self._name,))) or ''
|
||||
cursor.execute('SELECT arch, name, field_childs, id, type, ' \
|
||||
cursor.execute('SELECT arch, field_childs, id, type, ' \
|
||||
'inherit ' \
|
||||
'FROM ir_ui_view WHERE id = %s ' + where, (view_id,))
|
||||
else:
|
||||
cursor.execute('SELECT arch, name, field_childs, id, type, ' \
|
||||
cursor.execute('SELECT arch, field_childs, id, type, ' \
|
||||
'inherit ' \
|
||||
'FROM ir_ui_view ' \
|
||||
'WHERE model = %s AND type = %s AND inherit IS NULL ' \
|
||||
@@ -2167,14 +1902,14 @@ class ORM(object):
|
||||
sql_res = cursor.fetchone()
|
||||
if not sql_res:
|
||||
break
|
||||
test = sql_res[5]
|
||||
test = sql_res[4]
|
||||
view_id = test or sql_res[3]
|
||||
model = False
|
||||
|
||||
# if a view was found
|
||||
if sql_res:
|
||||
result['type'] = sql_res[4]
|
||||
result['view_id'] = sql_res[3]
|
||||
result['type'] = sql_res[3]
|
||||
result['view_id'] = sql_res[2]
|
||||
result['arch'] = sql_res[0]
|
||||
|
||||
def _inherit_apply_rec(result, inherit_id):
|
||||
@@ -2188,9 +1923,7 @@ class ORM(object):
|
||||
result = _inherit_apply_rec(result, view_id)
|
||||
return result
|
||||
|
||||
result['arch'] = _inherit_apply_rec(result['arch'], sql_res[3])
|
||||
|
||||
result['name'] = sql_res[1]
|
||||
result['arch'] = _inherit_apply_rec(result['arch'], sql_res[2])
|
||||
result['field_childs'] = sql_res[2] or False
|
||||
# otherwise, build some kind of default view
|
||||
else:
|
||||
@@ -2224,7 +1957,6 @@ class ORM(object):
|
||||
xml = ''
|
||||
result['type'] = view_type
|
||||
result['arch'] = xml
|
||||
result['name'] = 'default'
|
||||
result['field_childs'] = False
|
||||
result['view_id'] = 0
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from mx import DateTime as mdt
|
||||
import zipfile
|
||||
import version
|
||||
from config import CONFIG
|
||||
from netsvc import Logger, LOG_ERROR, LOG_WARNING
|
||||
|
||||
RE_FROM = re.compile('.* from "?([a-zA-Z_0-9]+)"?.*$')
|
||||
RE_INTO = re.compile('.* into "?([a-zA-Z_0-9]+)"?.*$')
|
||||
@@ -224,3 +225,259 @@ def init_db(cursor):
|
||||
(module_id, dependency))
|
||||
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||
|
||||
|
||||
|
||||
def table_exist(cursor, table_name):
|
||||
cursor.execute("SELECT relname FROM pg_class " \
|
||||
"WHERE relkind = 'r' AND relname = %s",
|
||||
(table_name,))
|
||||
return bool(cursor.rowcount)
|
||||
|
||||
|
||||
class table_handler:
|
||||
|
||||
def __init__(self, cursor, table_name, object_name=None, module_name=None):
|
||||
self.table_name = table_name
|
||||
self.table = {}
|
||||
self.constraint = []
|
||||
self.fk_deltype = {}
|
||||
self.index = {}
|
||||
self.field2module = {}
|
||||
self.module_name = module_name
|
||||
self.cursor = cursor
|
||||
|
||||
# Create new table if necessary
|
||||
if not table_exist(self.cursor, self.table_name):
|
||||
self.cursor.execute("CREATE TABLE %s " \
|
||||
"(id SERIAL NOT NULL, " \
|
||||
"PRIMARY KEY(id))"% self.table_name)
|
||||
|
||||
# Fetch columns definitions from the table
|
||||
self.cursor.execute("SELECT at.attname, at.attlen, "\
|
||||
"at.atttypmod, at.attnotnull, at.atthasdef, "\
|
||||
"ty.typname, "\
|
||||
"CASE WHEN at.attlen = -1 "\
|
||||
"THEN at.atttypmod-4 "\
|
||||
"ELSE at.attlen END as size "\
|
||||
"FROM pg_class cl "\
|
||||
"JOIN pg_attribute at on (cl.oid = at.attrelid) "\
|
||||
"JOIN pg_type ty on (at.atttypid = ty.oid) "\
|
||||
"WHERE cl.relname = %s AND at.attnum > 0",
|
||||
(table_name,))
|
||||
for line in self.cursor.fetchall():
|
||||
column, length, typmod, notnull, hasdef, typname, size = line
|
||||
self.table[column] = {
|
||||
"length": length,
|
||||
"typmod": typmod,
|
||||
"notnull": notnull ,
|
||||
"hasdef": hasdef,
|
||||
"size": size,
|
||||
"typname": typname}
|
||||
|
||||
# fetch constrains for the table
|
||||
self.cursor.execute("SELECT co.contype, co.confdeltype, at.attname, "\
|
||||
"cl2.relname, co.conname "\
|
||||
"FROM pg_constraint co "\
|
||||
"LEFT JOIN pg_class cl on (co.conrelid = cl.oid) "\
|
||||
"LEFT JOIN pg_class cl2 on (co.confrelid = cl2.oid) "\
|
||||
"LEFT JOIN pg_attribute at on (co.conkey[1] = at.attnum) "\
|
||||
"WHERE cl.relname = %s",
|
||||
(table_name,))
|
||||
for line in self.cursor.fetchall():
|
||||
contype, confdeltype, column, ref, conname = line
|
||||
if contype == 'f':
|
||||
self.fk_deltype[column] = confdeltype
|
||||
else:
|
||||
if conname not in self.constraint:
|
||||
self.constraint.append(conname)
|
||||
|
||||
# Fetch indexes defined for the table
|
||||
self.cursor.execute("SELECT cl2.relname "\
|
||||
"FROM pg_index ind "\
|
||||
"JOIN pg_class cl on (cl.oid = ind.indrelid) "\
|
||||
"JOIN pg_class cl2 on (cl2.oid = ind.indexrelid) "\
|
||||
"WHERE cl.relname = %s",
|
||||
(table_name,))
|
||||
self.index = [l[0] for l in self.cursor.fetchall()]
|
||||
|
||||
# Keep track of which module created each field
|
||||
if object_name is not None:
|
||||
self.cursor.execute('SELECT f.name, f.module '\
|
||||
'FROM ir_model_field f '\
|
||||
'JOIN ir_model m on (f.model=m.id) '\
|
||||
'WHERE m.model = %s',
|
||||
(object_name,)
|
||||
)
|
||||
for line in self.cursor.fetchall():
|
||||
self.field2module[line[0]] = line[1]
|
||||
|
||||
def migrate_column(self, column_name, column_type):
|
||||
|
||||
self.cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"RENAME COLUMN \"%s\" " \
|
||||
"TO temp_change_size" % \
|
||||
(self.table_name, column_name))
|
||||
self.cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"ADD COLUMN \"%s\" %s" % \
|
||||
(self.table_name, column_name, column_type))
|
||||
self.cursor.execute("UPDATE \"%s\" " \
|
||||
"SET \"%s\" = temp_change_size::%s" % \
|
||||
(self.table_name, column_name, column_type))
|
||||
self.cursor.execute("ALTER TABLE \"%s\" " \
|
||||
"DROP COLUMN temp_change_size" % \
|
||||
(self.table_name,))
|
||||
|
||||
|
||||
def add_raw_column(self, column_name, column_type, default_fun=None,
|
||||
field_size=None, migrate = True):
|
||||
if column_name in self.table:
|
||||
|
||||
#print ">>>",self.table_name, column_name, self.table[column_name]['typname'], self.table[column_name]['size'], column_type, field_size, "\n\n"
|
||||
|
||||
if not migrate:
|
||||
return
|
||||
base_type = column_type[0].lower()
|
||||
if not base_type == self.table[column_name]['typname']:
|
||||
raise Exception(
|
||||
'Unable to migrate column % s from %s to %s.' % \
|
||||
(column_name, self.table[column_name]['typname'], base_type))
|
||||
if not base_type == 'varchar':
|
||||
return
|
||||
if field_size == None:
|
||||
if self.table[column_name]['size'] > 0:
|
||||
self.migrate_column(column_name, base_type)
|
||||
return
|
||||
elif self.table[column_name]['size'] == field_size:
|
||||
return
|
||||
elif self.table[column_name]['size'] > 0 and \
|
||||
self.table[column_name]['size'] < field_size:
|
||||
self.migrate_column(column_name, base_type)
|
||||
else:
|
||||
raise Exception(
|
||||
'Unable to migrate column %s from varchar(%s) to varchar(%s).' % \
|
||||
(column_name, self.table[column_name]['size'], field_size))
|
||||
column_type = column_type[1]
|
||||
if column_name == 'operand': print column_type
|
||||
self.cursor.execute("ALTER TABLE %s ADD COLUMN \"%s\" %s"%
|
||||
(self.table_name, column_name, column_type))
|
||||
if default_fun is not None:
|
||||
default = default_fun(self.cursor, 0, {})
|
||||
if default: # XXX What about a boolean who's default false
|
||||
# or an default integer of 0 .. all default
|
||||
# fun should return None instead of false when
|
||||
# there is no default to put. (see action.py l.230)
|
||||
self.cursor.execute("UPDATE " + self.table_name + " "\
|
||||
"SET \"" + column_name + "\" = %s",
|
||||
(default,))
|
||||
|
||||
def add_m2m(self, column_name, other_table, relation_table, rtable_from, rtable_to):
|
||||
if not table_exist(self.cursor, other_table):
|
||||
raise Exception("table %s not found"%other_table)
|
||||
rtable = table_handler(
|
||||
self.cursor, relation_table, object_name=None,
|
||||
module_name= self.module_name)
|
||||
rtable.add_raw_column(rtable_from, ('int4', 'int4'))
|
||||
rtable.add_raw_column(rtable_to, ('int4', 'int4'))
|
||||
rtable.add_fk(rtable_from, self.table_name)
|
||||
rtable.add_fk(rtable_to, other_table)
|
||||
rtable.index_action(rtable_from, 'add')
|
||||
rtable.index_action(rtable_to, 'add')
|
||||
|
||||
def add_fk(self, column_name, reference, on_delete=None):
|
||||
on_delete_code = {
|
||||
'RESTRICT': 'r',
|
||||
'NO ACTION': 'a',
|
||||
'CASCADE': 'c',
|
||||
'SET NULL': 'n',
|
||||
'SET DEFAULT': 'd',
|
||||
}
|
||||
if on_delete is not None :
|
||||
on_delete = on_delete.upper()
|
||||
if on_delete not in on_delete_code:
|
||||
raise Exception('On delete action not supported !')
|
||||
else:
|
||||
on_delete = 'SET NULL'
|
||||
code = on_delete_code[on_delete]
|
||||
|
||||
if self.fk_deltype.get(column_name) == code:
|
||||
# The fk exist and the delete action is ok
|
||||
return
|
||||
self.cursor.execute("ALTER TABLE " + self.table_name + " " \
|
||||
"ADD FOREIGN KEY (\"" + column_name + "\") " \
|
||||
"REFERENCES " + reference + " " \
|
||||
"ON DELETE " + on_delete)
|
||||
|
||||
def index_action(self, column_name, action='add'):
|
||||
index_name = "%s_%s_index" % (self.table_name, column_name)
|
||||
|
||||
if action == 'add':
|
||||
if index_name in self.index:
|
||||
return
|
||||
self.cursor.execute("CREATE INDEX " + index_name + " " \
|
||||
"ON " + self.table_name + " (" + column_name + ")")
|
||||
elif action == 'remove':
|
||||
if self.field2module.get(column_name) != self.module_name:
|
||||
return
|
||||
|
||||
self.cursor.execute("SELECT * FROM pg_indexes " \
|
||||
"WHERE indexname = '%s'" %
|
||||
(index_name,))
|
||||
if self.cursor.rowcount:
|
||||
self.cursor.execute("DROP INDEX %s " % (index_name,))
|
||||
|
||||
|
||||
def not_null_action(self, column_name, action='add'):
|
||||
if column_name not in self.table:
|
||||
return
|
||||
|
||||
if action == 'add':
|
||||
if self.table[column_name]['notnull']:
|
||||
return
|
||||
self.cursor.execute('SELECT id FROM "%s" ' \
|
||||
'WHERE "%s" IS NULL' % \
|
||||
(self.table_name, column_name))
|
||||
if not self.cursor.rowcount:
|
||||
self.cursor.execute("ALTER TABLE " + self.table_name + " " \
|
||||
"ALTER COLUMN \"" + column_name +"\" " \
|
||||
"SET NOT NULL")
|
||||
else:
|
||||
logger = Logger()
|
||||
logger.notify_channel(
|
||||
'init',
|
||||
LOG_WARNING,
|
||||
'Unable to set column %s ' \
|
||||
'of table %s not null !\n'\
|
||||
'Try to re-run: ' \
|
||||
'trytond.py --update=module\n' \
|
||||
'If it doesn\'t work, update records and execute manually:\n' \
|
||||
'ALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % \
|
||||
(column_name, self.table_name, self.table_name, column_name))
|
||||
elif action == 'remove':
|
||||
if not self.table[column_name]['notnull']:
|
||||
return
|
||||
if self.field2module.get(column_name) != self.module_name:
|
||||
return
|
||||
self.cursor.execute("ALTER TABLE %s " \
|
||||
"ALTER COLUMN %s DROP NOT NULL"%
|
||||
(self.table_name, column_name))
|
||||
|
||||
def add_constraint(self, ident, constraint):
|
||||
ident = self.table_name + "_" + ident
|
||||
if ident in self.constraint:
|
||||
# This constrain already exist
|
||||
return
|
||||
try:
|
||||
self.cursor.execute('ALTER TABLE \"%s\" ' \
|
||||
'ADD CONSTRAINT \"%s\" %s' % \
|
||||
(self.table_name, ident, constraint,))
|
||||
except:
|
||||
raise
|
||||
logger = Logger()
|
||||
logger.notify_channel(
|
||||
'init', LOG_WARNING,
|
||||
'unable to add \'%s\' constraint on table %s !\n' \
|
||||
'If you want to have it, you should update the records and execute manually:\n'\
|
||||
'ALTER table %s ADD CONSTRAINT %s %s' % \
|
||||
(constraint, self.table_name, self.table_name,
|
||||
ident, constraint,))
|
||||
Reference in new issue
Block a user