diff --git a/module_prototyper/README.rst b/module_prototyper/README.rst index 4c890aa146a..9c4a6f0bc93 100644 --- a/module_prototyper/README.rst +++ b/module_prototyper/README.rst @@ -86,6 +86,7 @@ Contributors * El hadji Dem * Savoir-faire Linux * Vincent Vinet +* Juan Pablo Arias Maintainer ---------- diff --git a/module_prototyper/__init__.py b/module_prototyper/__init__.py index f01f7f1225a..113fd04ca2e 100644 --- a/module_prototyper/__init__.py +++ b/module_prototyper/__init__.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution diff --git a/module_prototyper/__openerp__.py b/module_prototyper/__openerp__.py index d9cde4b6494..3200e9bf265 100644 --- a/module_prototyper/__openerp__.py +++ b/module_prototyper/__openerp__.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution @@ -22,7 +22,7 @@ { 'name': 'Module Prototyper', - 'version': '8.0.0.3.0', + 'version': '8.0.0.4.0', 'author': 'Savoir-faire Linux, Odoo Community Association (OCA)', 'maintainer': 'Savoir-faire Linux', 'website': 'http://www.savoirfairelinux.com', diff --git a/module_prototyper/data/README.rst b/module_prototyper/data/README.rst index ba37f1bfb91..6f2e99e3414 100644 --- a/module_prototyper/data/README.rst +++ b/module_prototyper/data/README.rst @@ -14,21 +14,21 @@ Installation To install this module, you need to: - * do this ... +* do this ... Configuration ============= To configure this module, you need to: - * go to ... +* go to ... Usage ===== To use this module, you need to: - * go to ... +* go to ... .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot @@ -39,12 +39,12 @@ To use this module, you need to: For further information, please visit: - * https://www.odoo.com/forum/help-1 +* https://www.odoo.com/forum/help-1 Known issues / Roadmap ====================== - * ... +* ... Bug Tracker =========== diff --git a/module_prototyper/models/__init__.py b/module_prototyper/models/__init__.py index 995a201154a..f3fe586b3fd 100644 --- a/module_prototyper/models/__init__.py +++ b/module_prototyper/models/__init__.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution diff --git a/module_prototyper/models/ir_model_fields.py b/module_prototyper/models/ir_model_fields.py index e67df14f814..25f3db0ff40 100644 --- a/module_prototyper/models/ir_model_fields.py +++ b/module_prototyper/models/ir_model_fields.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution @@ -24,7 +24,7 @@ from openerp.tools.translate import _ -class ir_model_fields(models.Model): +class IrModelFields(models.Model): """Addition of text fields to fields.""" _inherit = "ir.model.fields" diff --git a/module_prototyper/models/licenses.py b/module_prototyper/models/licenses.py index 075d27a5923..9ecbe37927a 100644 --- a/module_prototyper/models/licenses.py +++ b/module_prototyper/models/licenses.py @@ -1,5 +1,5 @@ -# -*- encoding: utf-8 -*- -# ############################################################################# +# -*- coding: utf-8 -*- +############################################################################## # # OpenERP, Open Source Management Solution # This module copyright (C) 2010 - 2014 Savoir-faire Linux diff --git a/module_prototyper/models/module_prototyper.py b/module_prototyper/models/module_prototyper.py index bd3aaa15db6..6f4de3a323b 100644 --- a/module_prototyper/models/module_prototyper.py +++ b/module_prototyper/models/module_prototyper.py @@ -1,5 +1,5 @@ -# -*- encoding: utf-8 -*- -# ############################################################################# +# -*- coding: utf-8 -*- +############################################################################## # # OpenERP, Open Source Management Solution # This module copyright (C) 2010 - 2014 Savoir-faire Linux @@ -33,6 +33,7 @@ from openerp import models, api, fields from openerp.tools.safe_eval import safe_eval +from openerp.exceptions import ValidationError from . import licenses @@ -41,7 +42,14 @@ YEAR = date.today().year +class IrFilter(models.Model): + _inherit = 'ir.filters' + + sequence = fields.Integer() + + class ModulePrototyper(models.Model): + """Module Prototyper gathers different information from all over the database to build a prototype of module. We are calling it a prototype as it will most likely need to be reviewed @@ -201,6 +209,7 @@ def get_default_description(self): help=('Enter the list of workflow transitions that you have created ' 'and want to export in this module') ) + keep_external_ids = fields.Boolean(default=False) _env = None _data_files = () @@ -208,6 +217,20 @@ def get_default_description(self): _field_descriptions = None File_details = namedtuple('file_details', ['filename', 'filecontent']) template_path = '{}/../templates/'.format(os.path.dirname(__file__)) + MAGIC_FIELDS_KEY = ["create_date", "create_uid", "write_uid", "id", + "__last_update", "write_date", "display_name", "image"] + type_fields = ["xml", "html", "file", "char", "base64", + "int", "float", "list", "tuple"] + + @api.model + def set_keep_external_ids(self, bool): + """Set the the keep_external_ids from the wizard + keep_external_ids will help to update all external + ids or keep exsting ones. + :param bool: bool, wizard's value + """ + self.keep_external_ids = bool + pass @api.model def set_jinja_env(self, api_version): @@ -224,6 +247,8 @@ def set_jinja_env(self, api_version): os.path.join(self.template_path, api_version) ) ) + # Instanse methods that will be sent to file + self._env.globals.update({'len': len}) return self._env def set_field_descriptions(self): @@ -247,7 +272,7 @@ def set_field_descriptions(self): self._field_descriptions[field] = field_description @api.model - def generate_files(self): + def generate_files(self, fields_ignore=None): """ Generates the files from the details of the prototype. :return: tuple """ @@ -264,7 +289,7 @@ def generate_files(self): file_details.extend(self.generate_views_details()) file_details.extend(self.generate_menus_details()) file_details.append(self.generate_module_init_file_details()) - file_details.extend(self.generate_data_files()) + file_details.extend(self.generate_data_files(fields_ignore)) # must be the last as the other generations might add information # to put in the __openerp__: additional dependencies, views files, etc. file_details.append(self.generate_module_openerp_file_details()) @@ -298,7 +323,7 @@ def generate_module_openerp_file_details(self): '__openerp__.py.template', prototype=self, data_files=self._data_files, - demo_fiels=self._demo_files, + demo_files=self._demo_files, ) @api.model @@ -425,8 +450,42 @@ def generate_model_details(self, model, field_descriptions): fields=field_descriptions, ) + def topological_sort(self, items): + """ + Items must be provided in the form of an iterable, + where the key has a tuple. + [ + [item,[dependencies]], + [,[]], + [,[]] + ] + """ + provided = set() + while items: + remaining_items = [] + emitted = False + + for item, dependencies, records in items: + # + # Definir el metodo que ignora los fields_diff + # + if dependencies.issubset(provided): + yield item, records + provided.add(item) + emitted = True + else: + remaining_items.append((item, dependencies, records)) + + if not emitted: + raise ValidationError('The dependency-aware sorting\ + (topological sort) of the model names faild.\ + Probably the algorythm is getting a circular dependency.\ + Please refer to the source code.') + + items = remaining_items + @api.model - def generate_data_files(self): + def generate_data_files(self, fields_ignore=None): """ Generate data and demo files """ data, demo = {}, {} filters = [ @@ -444,13 +503,39 @@ def generate_data_files(self): target[model] |= model_obj.search(safe_eval(ir_filter.domain)) res = [] + + # Contains name of fields that will ignored + fields_obj = self.env['ir.model.fields'] + fields_ignore_names = [field.name for field in + fields_obj.browse(fields_ignore)] + for prefix, model_data, file_list in [ ('data', data, self._data_files), ('demo', demo, self._demo_files)]: + + items = [] + models = set(m for m, recs in model_data.iteritems()) + for model_name, records in model_data.iteritems(): + if not records: + raise ValidationError('There are no records\ + on a particular filter. Please check, that you\'re\ + only accessing filters that show records.') + dependencies = set() + for f in records._fields: + if f in fields_ignore_names: + continue + d = records._fields[f].comodel_name + if d is not None and d in models and d != model_name: + dependencies.add(d) + # ensure unique values of comodel_types + items.append([model_name, dependencies, records]) + + for model_name, records in self.topological_sort(items): fname = self.friendly_name(self.unprefix(model_name)) filename = '{0}/{1}.xml'.format(prefix, fname) self._data_files.append(filename) + records = self.fixup_data(records) res.append(self.generate_file_details( filename, @@ -513,6 +598,108 @@ def fixup_arch(cls, archstr): return lxml.etree.tostring(doc) + @classmethod + def order_data(cls, records): + + x = max([r.id for r in records]) + + def key(record): + level = 0 + + if hasattr(record, 'parent_id'): + parent = record.parent_id or False + + while parent: + level += 1 + parent = hasattr(parent, 'parent_id') and parent.parent_id + + return (level * x) + record.id + + return records.sorted(key=key) + + @api.model + def generate_external_id(self, records): + ir_model_data = records.sudo().env['ir.model.data'] + i = 0 + x = max([r.id for r in records]) + n = len(str(x)) + + if not self.keep_external_ids: + delete = ir_model_data.search([('model', '=', records._name)]) + delete.unlink() + + for r in records: + i += 1 + data = ir_model_data.search( + [('model', '=', r._name), ('res_id', '=', r.id)]) + + if data and self.keep_external_ids: + pass + elif not self.keep_external_ids: + name = '%s_%s' % (r._name.split(".").pop(), str(i).zfill(n)) + ir_model_data.create({ + 'model': r._name, + 'res_id': r.id, + 'module': self.name, + 'name': name, + }) + + @api.model + def fixup_data(cls, records): + records = cls.order_data(records) + cls.generate_external_id(records) + + # http://odoo-80.readthedocs.org/en/latest/reference/data.html + # http://stackoverflow.com/questions/26011102/openerp-odoo-model-relationship-xml-syntax + # For simplicity, we might be able to work with the eval and the obj, + # that is available in the context and browse it by the id, + # that we already have + + recs_lst = [] + for r in records: + ext_id = r.get_external_id().values()[0] + fields_lst = [] + + for field, val in r.read()[0].iteritems(): + if field in cls.MAGIC_FIELDS_KEY: + continue + + ref = False + field_type = False + + # Only catch external ids, if it is a relational field + # TODO: Verify, if fields._RelationalMulti (One2many&Many2many) + # are also represented in an xml with the ref attribute + # Else there would need to be added an if statement which + # constructs the eval attribute accordingly. + if isinstance(r._fields[field], fields._Relational): + ref = ",".join(r[field].get_external_id().values()) + val = False + + # see:https://www.odoo.com/documentation/8.0/reference/data.html + if r.fields_get()[field]['type'] in cls.type_fields: + field_type = r.fields_get()[field]['type'] + + # Ref data to list + # If name of module is in ref it is deleted + # (Data fields not referring to himself) + if ref and cls.name in ref: + ref = ref.replace(cls.name + ".", "") + ref = ref.split(",") + elif ref: + ref = ref.split(",") + else: + ref = [] + + fields_lst.append([field, val, ref, field_type]) + + # Ordering fields on each record in alfabetical order. + fields_lst = sorted(fields_lst, key=lambda k: k[0]) + # add external id and fields list to rec_lst + recs_lst.extend([(ext_id, fields_lst)]) + + return sorted(recs_lst, key=lambda k: k[0]) + @api.model def generate_file_details(self, filename, template, **kwargs): """ generate file details from jinja2 template. @@ -540,6 +727,62 @@ def generate_file_details(self, filename, template, **kwargs): ) return self.File_details(filename, template.render(kwargs)) + @api.model + def deps_detect(self): + """ Detect depedencies of the data fields. + :return: list + """ + + assert self._env is not None, \ + 'Run set_env(api_version) before to generate files.' + + self.generate_models_details() + + data, demo = {}, {} + filters = [ + (data, ir_filter) + for ir_filter in self.data_ids + ] + [ + (demo, ir_filter) + for ir_filter in self.demo_ids + ] + + for target, ir_filter in filters: + model = ir_filter.model_id + model_obj = self.env[model] + target.setdefault(model, model_obj.browse([])) + target[model] |= model_obj.search(safe_eval(ir_filter.domain)) + + fields_dep = [] + fields_obj = self.env['ir.model.fields'] + model_obj = self.env['ir.model'] + + for prefix, model_data, file_list in [ + ('data', data, self._data_files), + ('demo', demo, self._demo_files)]: + + models = set(m for m, recs in model_data.iteritems()) + + for model_name, records in model_data.iteritems(): + if not records: + raise ValidationError('There are no records on a\ + particular filter. Please check,\ + that you\'re only accessing filters\ + that show records.') + for f in records._fields: + dep = records._fields[f].comodel_name + if dep is not None and dep in models and dep != model_name: + # Save fields that possibly contains circular + # dependency + model_id = model_obj.search( + [['model', '=', model_name]]).id + field_id = fields_obj.search( + [['name', '=', f], + ['model_id', '=', model_id]]) + fields_dep.append(field_id.id) + + return fields_dep + # Utility functions for rendering templates def wrap(text, **kwargs): diff --git a/module_prototyper/templates/8.0/README.rst.template b/module_prototyper/templates/8.0/README.rst.template new file mode 100644 index 00000000000..f6730dfab87 --- /dev/null +++ b/module_prototyper/templates/8.0/README.rst.template @@ -0,0 +1,3 @@ +{% block body %} +{{ prototype.description }} +{% endblock %} diff --git a/module_prototyper/templates/8.0/__openerp__.py.template b/module_prototyper/templates/8.0/__openerp__.py.template index 5c8e687ec74..9db0c7edbb9 100644 --- a/module_prototyper/templates/8.0/__openerp__.py.template +++ b/module_prototyper/templates/8.0/__openerp__.py.template @@ -12,15 +12,8 @@ # Categories can be used to filter modules in modules listing # Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml # noqa # for the full list - 'category': '{{ prototype.with_context({}).category_id.name }}',{# In english please! #} + 'category': '{{ prototype.with_context({}).category_id.name }}', 'summary': '{{ prototype.summary }}', - 'description': """ -{{ prototype.description }} - -* Module exported by the Module Prototyper module for version 8.0. -* If you have any questions, please contact Savoir-faire Linux -(support@savoirfairelinux.com) -""", # any module necessary for this one to work correctly 'depends': [ @@ -41,11 +34,11 @@ # only loaded in demonstration mode 'demo': [ {% for demo_file in prototype.demo_ids %} - '{{ demo_file.name }}', + '{{ demo_file }}', {% endfor %} ], - # used for Javascript Web CLient Testing with QUnit / PhantomJS + # used for Javascript Web Client Testing with QUnit / PhantomJS # https://www.odoo.com/documentation/8.0/reference/javascript.html#testing-in-odoo-web-client # noqa 'js': [], 'css': [], diff --git a/module_prototyper/templates/8.0/data/model_name.xml.template b/module_prototyper/templates/8.0/data/model_name.xml.template index 7b25f11f800..306e0d9d6ae 100644 --- a/module_prototyper/templates/8.0/data/model_name.xml.template +++ b/module_prototyper/templates/8.0/data/model_name.xml.template @@ -1,8 +1,18 @@ + {% for record in records %} - {{ data }} + + {% for key, val, ref, field_type in record[1] %} + 1 %} eval="[(6,0,[{% for r in ref %}ref('{{ r }}'),{% endfor %}])]"{% endif %}{% if field_type %} type="{{ field_type }}"{% endif %}{% if val %}>{{ val }}{% else %}/>{% endif %} + {% endfor %} + + + {% if not loop.last %} + + {% endif %} + {% endfor %} diff --git a/module_prototyper/templates/8.0/header.template b/module_prototyper/templates/8.0/header.template index c09b2426ba6..9134cba5b99 100644 --- a/module_prototyper/templates/8.0/header.template +++ b/module_prototyper/templates/8.0/header.template @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # # Odoo, Open Source Management Solution diff --git a/module_prototyper/templates/8.0/models/model_name.py.template b/module_prototyper/templates/8.0/models/model_name.py.template index b35202d38f5..e0e713af651 100644 --- a/module_prototyper/templates/8.0/models/model_name.py.template +++ b/module_prototyper/templates/8.0/models/model_name.py.template @@ -23,7 +23,7 @@ class {{ unprefix(name) }}(models.Model): string=_("{{ field.field_description }}"), required={{ field.required }}, translate={{ field.translate }}, - readonly={{ field.readonly }} + readonly={{ field.readonly }}, {% if field.size %} size={{ field.size }}, {% endif %} diff --git a/module_prototyper/tests/__init__.py b/module_prototyper/tests/__init__.py index aff3c1c83fe..ff14dd0a5b1 100644 --- a/module_prototyper/tests/__init__.py +++ b/module_prototyper/tests/__init__.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution diff --git a/module_prototyper/tests/test_prototype.py b/module_prototyper/tests/test_prototype.py index 76d3c8fdc51..f5b11e5a5cb 100644 --- a/module_prototyper/tests/test_prototype.py +++ b/module_prototyper/tests/test_prototype.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- # +# -*- coding: utf-8 -*- # # OpenERP, Open Source Management Solution # This module copyright (C) 2013 Savoir-faire Linux # (). diff --git a/module_prototyper/tests/test_prototype_module_export.py b/module_prototyper/tests/test_prototype_module_export.py index e58cd1b9037..092aad4e3ad 100644 --- a/module_prototyper/tests/test_prototype_module_export.py +++ b/module_prototyper/tests/test_prototype_module_export.py @@ -1,5 +1,5 @@ -# -*- encoding: utf-8 -*- -# ############################################################################# +# -*- coding: utf-8 -*- +############################################################################## # # OpenERP, Open Source Management Solution # This module copyright (C) 2010 - 2014 Savoir-faire Linux @@ -24,9 +24,9 @@ import StringIO -class test_prototype_module_export(common.TransactionCase): +class TestPrototypeModuleExport(common.TransactionCase): def setUp(self): - super(test_prototype_module_export, self).setUp() + super(TestPrototypeModuleExport, self).setUp() self.main_model = self.env['module_prototyper.module.export'] self.prototype_model = self.env['module_prototyper'] self.module_category_model = self.env[ diff --git a/module_prototyper/wizard/__init__.py b/module_prototyper/wizard/__init__.py index bb0eb2ebe82..bae80651740 100644 --- a/module_prototyper/wizard/__init__.py +++ b/module_prototyper/wizard/__init__.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution diff --git a/module_prototyper/wizard/module_prototyper_module_export.py b/module_prototyper/wizard/module_prototyper_module_export.py index 560bf591c0b..1e2dd9c1903 100644 --- a/module_prototyper/wizard/module_prototyper_module_export.py +++ b/module_prototyper/wizard/module_prototyper_module_export.py @@ -1,5 +1,5 @@ -# -*- encoding: utf-8 -*- -# ############################################################################# +# -*- coding: utf-8 -*- +############################################################################## # # OpenERP, Open Source Management Solution # This module copyright (C) 2010 - 2014 Savoir-faire Linux @@ -45,11 +45,78 @@ class PrototypeModuleExport(models.TransientModel): state = fields.Selection( [ ('choose', 'choose'), # choose version + ('dep', 'dep'), # dependencies ('get', 'get') # get module ], default='choose' ) + keep_external_ids = fields.Boolean( + string="Preserve existing external IDs?", + help="""Useful, if you are using this module for + generating module data from different sources. + Keep for example, when shipping data updates + with this model. You may well need to still + manually clean up and mix in the results.""", + default=False + ) + + fields_selected = fields.Many2many( + string='Fields Selected', + required=False, + readonly=False, + index=False, + default=None, + help=False, + comodel_name='ir.model.fields', + domain=[], + context={}, + limit=None, + ) + + fields_selected_initial = fields.Many2many( + string='Fields Selected Difference', + required=False, + readonly=False, + index=False, + default=None, + help=False, + comodel_name='ir.model.fields', + domain=[], + context={}, + limit=None + ) + + fields_not_selected = fields.Many2many( + string='Fields Not Selected', + required=False, + readonly=False, + index=False, + default=None, + help=False, + comodel_name='ir.model.fields', + domain=[], + context={}, + limit=None, + compute='_compute_fields_not_selected', + ) + + @api.one + @api.depends('fields_selected', 'fields_not_selected') + def _compute_fields_not_selected(self): + # Diferentes between fields initial and actual + fields_ini = [field.id for field in self.fields_selected_initial] + fields_act = [field.id for field in self.fields_selected] + self.fields_not_selected = list(set(fields_ini) - set(fields_act)) + + @api.one + @api.onchange('fields_not_selected') + def onchange_fields_not_selected(self): + # Reformate self.fields_selected (If change fields_not_selected) + fields_ini = [field.id for field in self.fields_selected_initial] + fields_diff = [field.id for field in self.fields_not_selected] + self.fields_selected = list(set(fields_ini) - set(fields_diff)) + @api.model def action_export(self, ids): """ @@ -73,7 +140,40 @@ def action_export(self, ids): [self._context.get('active_id')] ) - zip_details = self.zip_files(wizard, prototypes) + # Validate if are not evaluated the depedencies resolve + # if the wizard was execute and returned a wizard fields_diff exists + # if the method dep_resolve not detect ciclyc dependencies + # (not return a wizard) continue to export files. + if not wizard.fields_selected and wizard.state != "dep": + fields_dependencies = self.dep_resolve(wizard, prototypes) + + if fields_dependencies: + wizard.write( + { + 'fields_selected': [(6, 1, fields_dependencies,)], + 'fields_selected_initial': [ + (6, 1, fields_dependencies,)], + 'state': 'dep', + } + ) + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'module_prototyper.module.export', + 'view_mode': 'form', + 'view_type': 'form', + 'res_id': wizard.id, + 'views': [(False, 'form')], + 'target': 'new', + 'context': self._context, + } + + # Recive of context the fields_not_selected because it's a wizard + fields_ignore = [] + if 'fields_not_selected' in self._context.keys(): + fields_ignore = self._context['fields_not_selected'][0][2] + + zip_details = self.zip_files(wizard, prototypes, fields_ignore) if len(prototypes) == 1: zip_name = prototypes[0].name @@ -99,7 +199,7 @@ def action_export(self, ids): } @staticmethod - def zip_files(wizard, prototypes): + def zip_files(wizard, prototypes, fields_ignore=None): """Takes a set of file and zips them. :param file_details: tuple (filename, file_content) :return: tuple (zip_file, stringIO) @@ -114,11 +214,15 @@ def zip_files(wizard, prototypes): # files with. prototype.set_jinja_env(wizard.api_version) + # will let the user decide on the wizard wether he wants + # to kkep external ids of the modules data or set fresh ones + prototype.set_keep_external_ids(wizard.keep_external_ids) + # generate_files ask the prototype to investigate the input and # to generate the file templates according to it. zip_files, # in another hand, put all the template files into a package # ready to be saved by the user. - file_details = prototype.generate_files() + file_details = prototype.generate_files(fields_ignore) for filename, file_content in file_details: if isinstance(file_content, unicode): file_content = file_content.encode('utf-8') @@ -130,3 +234,18 @@ def zip_files(wizard, prototypes): target.writestr(info, file_content) return zip_details(zip_file=target, stringIO=out) + + @api.model + def dep_resolve(self, wizard, prototypes): + # This method return a wizard if are ciclyc dependencies, + # else return void + + fields = [] + + # Create a list with field that possible have ciclyc dependencies + for prototype in prototypes: + prototype.set_jinja_env(wizard.api_version) + prototype.set_keep_external_ids(wizard.keep_external_ids) + fields.extend(prototype.deps_detect()) + + return fields diff --git a/module_prototyper/wizard/module_prototyper_module_export_view.xml b/module_prototyper/wizard/module_prototyper_module_export_view.xml index d4678ebf572..61727b95ba1 100644 --- a/module_prototyper/wizard/module_prototyper_module_export_view.xml +++ b/module_prototyper/wizard/module_prototyper_module_export_view.xml @@ -12,6 +12,7 @@ + @@ -20,10 +21,42 @@

Export Complete

Here is the exported module:

+
+

Circular dependency manual cleaning

+

We cannot programmatically solve this task, so we need your manual interaction. What you see is a list of circular dependency fields candidates in the upper list and the list of excluded fields in the lower list. A circular dependency cannot be exported, so we need to break it up. A circular dependency is called when Field X of Model A refers to Model B and Field Y of Model B refers to model A, so A refers to B and B, in turn, also refers to A. This way we cannot sort ("toposort") records and files in the correct way and export will fail.

+

Field Candidates:

+

Please identify in this list all circular dependencies, if any. Once identified, please delete one member of the circular dependency "ring" of your choice. It doesn't really matter which one. The deleted fields will appear in the excluded list below.

+ + + + + + + + + +
Example: account.account <-> account.tax models: account.account have tax_ids field (related with account.tax) and account.tax have account_paid_id and account_collected_id fields (related with account.account). In this case it is necessary to exclude tax_ids or account_paid_id and account_collected_id. This will prevent circular dependency in xml export.
+ +
+

List of excluded fields (during export):

+ + + + + + + + + +
+
+