Browse Source
Merge pull request #102 from savoirfairelinux/7.0-base_location_uniq_constraint
Merge pull request #102 from savoirfairelinux/7.0-base_location_uniq_constraint
7.0 - [IMP] Add unique constraint on res.better.zippull/131/head
Yannick Vaucher
10 years ago
5 changed files with 217 additions and 1 deletions
-
2base_location/__openerp__.py
-
5base_location/better_zip.py
-
4base_location/i18n/base_location.pot
-
5base_location/i18n/fr.po
-
202base_location/migrations/7.0.1.0/post-migration.py
@ -0,0 +1,202 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Contributor: David Dufresne <david.dufresne@savoirfairelinux.com> |
||||
|
# Sandy Carter <sandy.carter@savoirfairelinux.com> |
||||
|
# |
||||
|
# This program is free software: you can redistribute it and/or modify |
||||
|
# it under the terms of the GNU Affero General Public License as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################## |
||||
|
|
||||
|
from openerp import pooler, SUPERUSER_ID |
||||
|
from itertools import groupby |
||||
|
from operator import attrgetter |
||||
|
|
||||
|
|
||||
|
def remove_sql_constraint_duplicates(cr, model, constraint_attrs): |
||||
|
""" |
||||
|
This function was copied from OpenUpgrade |
||||
|
|
||||
|
Remove all duplicates after a sql constraint is applied on a model. |
||||
|
|
||||
|
For every field many2one and many2many with the given model as relation, |
||||
|
change the duplicate ids with the id of the record kept. |
||||
|
|
||||
|
This script must be called in post-migration so that the model being |
||||
|
edited can be accessed through the orm. |
||||
|
|
||||
|
When upgrading the module, if there are duplicates, integrity errors |
||||
|
will be raised before the script is run but this will not prevent |
||||
|
the script from running. |
||||
|
|
||||
|
:param model: the model on witch the constraint is applied |
||||
|
:param constraint_attrs: a list of string containing the fields that |
||||
|
form the uniq key |
||||
|
""" |
||||
|
pool = pooler.get_pool(cr.dbname) |
||||
|
model_pool = pool[model] |
||||
|
model_table = model_pool._table |
||||
|
|
||||
|
# Get all fields with the given model as many2one relation |
||||
|
field_pool = pool['ir.model.fields'] |
||||
|
field_m2o_ids = field_pool.search(cr, SUPERUSER_ID, [ |
||||
|
('relation', '=', model), |
||||
|
('ttype', '=', 'many2one'), |
||||
|
]) |
||||
|
# List of tables where to look for duplicates |
||||
|
# This is trivial for many2one relations |
||||
|
tables_to_lookup = [ |
||||
|
( |
||||
|
pool[field.model_id.model]._table, |
||||
|
field.name, 'many2one' |
||||
|
) for field in field_pool.browse(cr, SUPERUSER_ID, field_m2o_ids) |
||||
|
] |
||||
|
|
||||
|
# For many2many relations, we need to check over the existing |
||||
|
# foreign keys in the database in order to find the tables |
||||
|
|
||||
|
# Get all fields with the given model as many2many relation |
||||
|
field_m2m_ids = field_pool.search(cr, SUPERUSER_ID, [ |
||||
|
('relation', '=', model), |
||||
|
('ttype', '=', 'many2many'), |
||||
|
]) |
||||
|
fields_m2m = field_pool.browse(cr, SUPERUSER_ID, field_m2m_ids) |
||||
|
|
||||
|
for field in fields_m2m: |
||||
|
|
||||
|
other_model_table = pool[field.model_id.model]._table |
||||
|
|
||||
|
# Get all primary key constraints for the given table |
||||
|
query = "SELECT " \ |
||||
|
" tc.table_name, kcu.column_name, ccu.table_name " \ |
||||
|
"FROM " \ |
||||
|
" information_schema.table_constraints AS tc " \ |
||||
|
" JOIN information_schema.key_column_usage AS kcu " \ |
||||
|
" ON tc.constraint_name = kcu.constraint_name " \ |
||||
|
" JOIN information_schema.constraint_column_usage AS ccu " \ |
||||
|
" ON ccu.constraint_name = tc.constraint_name " \ |
||||
|
"WHERE constraint_type = 'FOREIGN KEY' " \ |
||||
|
" and ccu.table_name " \ |
||||
|
" in ('%(model_table)s', '%(other_model_table)s') " \ |
||||
|
" ORDER BY tc.table_name;" % { |
||||
|
'model_table': model_table, |
||||
|
'other_model_table': other_model_table |
||||
|
} |
||||
|
|
||||
|
cr.execute(query) |
||||
|
for key, group in groupby(cr.fetchall(), key=lambda c: c[0]): |
||||
|
constraints = list(group) |
||||
|
|
||||
|
model_field = next( |
||||
|
(c[1] for c in constraints if c[2] == model_table), False) |
||||
|
other_field = next( |
||||
|
(c[1] for c in constraints if c[2] == other_model_table), False |
||||
|
) |
||||
|
|
||||
|
if model_field and other_field: |
||||
|
# Add the current table to the list of tables where to look |
||||
|
# for duplicates |
||||
|
tables_to_lookup.append(( |
||||
|
key, model_field, 'many2many', other_field)) |
||||
|
|
||||
|
# Get all records |
||||
|
record_ids = model_pool.search(cr, SUPERUSER_ID, []) |
||||
|
records = model_pool.browse(cr, SUPERUSER_ID, record_ids) |
||||
|
|
||||
|
# Sort records by the constraint attributes |
||||
|
# so that they can be grouped with itertools.groupby |
||||
|
records.sort(key=attrgetter(*constraint_attrs)) |
||||
|
|
||||
|
for key, group in groupby(records, key=lambda x: tuple( |
||||
|
x[attr] for attr in constraint_attrs) |
||||
|
): |
||||
|
grouped_records = list(group) |
||||
|
|
||||
|
if len(grouped_records) > 1: |
||||
|
# Define a record to keep |
||||
|
new_record_id = grouped_records[0].id |
||||
|
|
||||
|
# All other records are to remove |
||||
|
old_record_ids = [z.id for z in grouped_records[1:]] |
||||
|
|
||||
|
all_record_ids = old_record_ids + [new_record_id] |
||||
|
|
||||
|
# Replace every many2one record in the database that has an old |
||||
|
# record as value with the record to keep |
||||
|
|
||||
|
for table in tables_to_lookup: |
||||
|
table_name = table[0] |
||||
|
|
||||
|
# Prevent the upgrade script to create duplicates |
||||
|
# in the many2many relation table and raise a constraint error |
||||
|
if table[2] == 'many2many': |
||||
|
cr.execute( |
||||
|
" SELECT t.%(other_field)s, t.%(field_name)s " |
||||
|
" FROM %(table_name)s as t" |
||||
|
" WHERE %(field_name)s " |
||||
|
" in %(all_record_ids)s " |
||||
|
" ORDER BY %(other_field)s" % |
||||
|
{ |
||||
|
'table_name': table_name, |
||||
|
'field_name': table[1], |
||||
|
'other_field': table[3], |
||||
|
'all_record_ids': tuple(all_record_ids), |
||||
|
}) |
||||
|
|
||||
|
for k, group_to_check in groupby( |
||||
|
cr.fetchall(), lambda rec: rec[0] |
||||
|
): |
||||
|
group_to_check = list(group_to_check) |
||||
|
if len(group_to_check) > 1: |
||||
|
for rec_to_unlink in group_to_check[1:]: |
||||
|
cr.execute( |
||||
|
" DELETE FROM %(table_name)s " |
||||
|
" WHERE %(field_name)s = %(field_value)s" |
||||
|
" AND %(other_field)s " |
||||
|
" = %(other_field_value)s" % |
||||
|
{ |
||||
|
'table_name': table_name, |
||||
|
'field_name': table[1], |
||||
|
'field_value': rec_to_unlink[1], |
||||
|
'other_field': table[3], |
||||
|
'other_field_value': rec_to_unlink[0], |
||||
|
}) |
||||
|
|
||||
|
# Main upgrade script |
||||
|
cr.execute( |
||||
|
" UPDATE %(table_name)s" |
||||
|
" SET %(field_name)s = %(new_value)s" |
||||
|
" WHERE %(field_name)s %(old_record_ids)s;" % |
||||
|
{ |
||||
|
'table_name': table_name, |
||||
|
'field_name': table[1], |
||||
|
'new_value': new_record_id, |
||||
|
'old_record_ids': len(old_record_ids) > 1 and |
||||
|
'in %s' % (tuple(old_record_ids),) or '= %s' % |
||||
|
old_record_ids[0] |
||||
|
}) |
||||
|
|
||||
|
model_pool.unlink(cr, SUPERUSER_ID, old_record_ids) |
||||
|
|
||||
|
|
||||
|
def migrate(cr, version): |
||||
|
""" |
||||
|
Remove duplicated locations |
||||
|
""" |
||||
|
if not version: |
||||
|
return |
||||
|
|
||||
|
constraint_attrs = ['name', 'city', 'state_id', 'country_id'] |
||||
|
remove_sql_constraint_duplicates( |
||||
|
cr, 'res.better.zip', constraint_attrs) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue