diff --git a/unserialize_field/__openerp__.py b/unserialize_field/__openerp__.py index 8d6d30be0..b639ce348 100644 --- a/unserialize_field/__openerp__.py +++ b/unserialize_field/__openerp__.py @@ -23,13 +23,23 @@ { 'name': 'Make database fields from fields that live in serialized fields', 'version': '1.0', - 'description': """To be able to search for fields with standard methods, -they have to be database fields. This addon makes it possible to unserialize -them afterwards.""", + 'description': """ +Sparse, or serialized fields do not live as a column in the database table. +Instead, their values are stored in JSON arrays in a separate field. As a +result, these fields cannot be searched or sorted by. + +If such a sparse field is created as 'manual', this module can unserialize +the field. Its field definition and values will then be migrated to a +proper database column. A real life use case where you encounter many +of such fields is the Magento-OpenERP connector. + +For technical reasons, many2many and one2many fields are not supported. +""", 'author': 'Therp BV', 'website': 'http://www.therp.nl', + 'version': '1.0', "category": "Tools", - "depends": [], + "depends": ['base'], "data": ['ir_model_fields.xml'], 'installable': True, 'active': False, diff --git a/unserialize_field/ir_model_fields.py b/unserialize_field/ir_model_fields.py index ea5d25cf4..c5b5699f3 100644 --- a/unserialize_field/ir_model_fields.py +++ b/unserialize_field/ir_model_fields.py @@ -19,91 +19,97 @@ # along with this program. If not, see . # ############################################################################## -from openerp.osv.orm import Model -from openerp.osv import fields +from openerp.osv import orm +from openerp.tools.translate import _ -class ir_model_fields(Model): +class ir_model_fields(orm.Model): _inherit = 'ir.model.fields' def action_unserialize_field(self, cr, uid, ids, context=None): step = 1000 offset = 0 - for this in self.browse(cr, uid, ids, context=context): - pool_obj = self.pool.get(this.model_id.model) - needs_write = self.create_database_column( - cr, uid, pool_obj, this.name) - while needs_write: - ids = pool_obj.search( + # Prevent _auto_init to commit the transaction + # before the data is migrated safely + commit_org = cr.commit + cr.commit = lambda *args: None + + try: + for this in self.browse(cr, uid, ids, context=context): + pool_obj = self.pool.get(this.model_id.model) + self.create_database_column(cr, uid, pool_obj, this.name, + context=context) + while True: + ids = pool_obj.search( cr, uid, [(this.serialization_field_id.name, '!=', '{}')], offset=offset*step, limit=step, context=context) - if not ids: - break - for data in pool_obj.read(cr, uid, ids, - [this.serialization_field_id.name], - context=context): - self.unserialize_field(cr, uid, pool_obj, data, - this.serialization_field_id.name, - this.name, context=context) - offset += 1 + if not ids: + break + for data in pool_obj.read(cr, uid, ids, + [this.serialization_field_id.name], + context=context): + self.unserialize_field(cr, uid, pool_obj, data, + this.serialization_field_id.name, + this.name, context=context) + offset += 1 + finally: + cr.commit = commit_org + return True - def create_database_column(self, cr, uid, pool_obj, field_name): - needs_write = True + def create_database_column(self, cr, uid, pool_obj, field_name, + context=None): old = pool_obj._columns[field_name] - field_declaration_args = [] - field_declaration_kwargs = dict( - manual=old.manual, - string=old.string, - required=old.required, - readonly=old.readonly, - domain=old._domain, - context=old._context, - states=old.states, - priority=old.priority, - change_default=old.change_default, - size=old.size, - ondelete=old.ondelete, - translate=old.translate, - select=old.select, - ) - - if old._type == 'many2one': - field_declaration_args = [old._obj] - elif old._type == 'selection': - field_declaration_args = [old.selection] - elif old._type == 'one2many': - field_declaration_args = [old._obj, old._fields_id] - field_declaration_kwargs['limit'] = old._limit - needs_write = False - elif old._type == 'many2many': - field_declaration_args = [old._obj] - field_declaration_kwargs['rel'] = old._rel - field_declaration_kwargs['id1'] = old._id1 - field_declaration_kwargs['id2'] = old._id2 - field_declaration_kwargs['limit'] = old._limit - needs_write = False - - field_declaration = getattr(fields, old._type)( - *field_declaration_args, - **field_declaration_kwargs) + if not old.manual: + raise orm.except_orm( + _('Error'), + _('This operation can only be performed on manual fields')) + if old._type == 'many2many': + # Cross table name length of manually created many2many + # fields can easily become too large. Although it would + # probably work if the table name length was within bounds, + # this scenario has not been tested because of this limitation. + raise orm.except_orm( + _("Error"), + _("Many2many fields are not supported. See " + "https://bugs.launchpad.net/openobject-server/+bug/1174078 " + "for more information")) + if old._type == 'one2many': + # How to get a safe field name for the relation field + # on the target model? + raise orm.except_orm( + _("Error"), + _("One2many fields are not handled yet")) + + # ORM prohibits to change the 'storing system' of the field + cr.execute(""" + UPDATE ir_model_fields + SET serialization_field_id = NULL + WHERE name = %s and model = %s + """, (field_name, pool_obj._name)) - pool_obj._columns[field_name] = field_declaration + del pool_obj._columns[field_name] + pool_obj.__init__(self.pool, cr) pool_obj._auto_init(cr, {'update_custom_fields': True}) - return needs_write def unserialize_field(self, cr, uid, pool_obj, read_record, serialization_field_name, field_name, context=None): - if not field_name in read_record[serialization_field_name]: + serialized_values = read_record[serialization_field_name] + if not field_name in serialized_values: return False - pool_obj.write( + + value = serialized_values.pop(field_name) + if pool_obj._columns[field_name]._type in ('many2many', 'one2many'): + value = [(6, 0, value)] + + return pool_obj.write( cr, uid, read_record['id'], { - field_name: - read_record[serialization_field_name][field_name], + field_name: value, + serialization_field_name: serialized_values, }, context=context) - return True + diff --git a/unserialize_field/ir_model_fields.xml b/unserialize_field/ir_model_fields.xml index 17f6701bb..4a92c4d39 100644 --- a/unserialize_field/ir_model_fields.xml +++ b/unserialize_field/ir_model_fields.xml @@ -16,5 +16,21 @@ + + + form + ir.model + + + + +