Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 233 additions & 3 deletions module_prototyper/models/module_prototyper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -201,13 +209,28 @@ 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 = ()
_demo_files = ()
_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):
Expand Down Expand Up @@ -247,7 +270,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
"""
Expand All @@ -264,7 +287,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())
Expand Down Expand Up @@ -425,8 +448,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 = [
Expand All @@ -444,13 +501,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,
Expand Down Expand Up @@ -513,6 +596,97 @@ 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']

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.
Expand Down Expand Up @@ -540,6 +714,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):
Expand Down
12 changes: 11 additions & 1 deletion module_prototyper/templates/8.0/data/model_name.xml.template
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
<?xml version="1.0"?>
<openerp>
<data>
{% for record in records %}

{{ data }}
<record id="{{ record[0] }}" model="{{ model }}">
{% for key, val, ref, field_type in record[1] %}
<field name="{{ key }}"{% if ref %} ref="{{ ref }}"{% endif %}{% if field_type %} type="{{ field_type }}"{% endif %}{% if val %}>{{ val }}</field>{% else %}/>{% endif %}

{% endfor %}
</record>

{% if not loop.last %}

{% endif %}
{% endfor %}
</data>
</openerp>
Loading