empty selection in m2m field template
[FIX] other licenses, return lines as well [FIX] license not shown in __oe__ [FIX] unprefix more names, try to get _name/_inherit right [IMP] group by module in zip [FIX] fix category and summary being on same line [FIX] fix export test [IMP] add tabs for reports/security/workflow/data + partial data/demo generation unprefix model names in __init__ [FIX] fix data file names in __openerp__.py [IMP] move Data&Demo after Interface in view [FIX] unprefix view file names [IMP] remove prefixes from field attrs in views [FIX] encode files in zip to utf-8, remove trailing comma in menu groups remove unused variable in tests remove AGPL3 or later from license choices: not in base module choicespull/107/head
parent
6dfed357d7
commit
2eebf0255a
|
@ -65,6 +65,7 @@ Contributors
|
||||||
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com>
|
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com>
|
||||||
* El hadji Dem <elhadji.dem@savoirfairelinux.com>
|
* El hadji Dem <elhadji.dem@savoirfairelinux.com>
|
||||||
* Savoir-faire Linux <support@savoirfairelinux.com>
|
* Savoir-faire Linux <support@savoirfairelinux.com>
|
||||||
|
* Vincent Vinet <vincent.vinet@savoirfairelinux.com>
|
||||||
|
|
||||||
Maintainer
|
Maintainer
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -42,7 +42,7 @@ class ir_model_fields(models.Model):
|
||||||
"relation table"),
|
"relation table"),
|
||||||
)
|
)
|
||||||
limit = fields.Integer('Read limit', help=_("Read limit"))
|
limit = fields.Integer('Read limit', help=_("Read limit"))
|
||||||
context = fields.Char(
|
client_context = fields.Char(
|
||||||
'Context',
|
'Context',
|
||||||
help=_("Context to use on the client side when handling the field "
|
help=_("Context to use on the client side when handling the field "
|
||||||
"(python dictionary)"),
|
"(python dictionary)"),
|
||||||
|
|
|
@ -68,6 +68,6 @@ def get_license_text(license):
|
||||||
name, version = GPL_LICENSES[license]
|
name, version = GPL_LICENSES[license]
|
||||||
return BASE_GPL.format(name=name, version=version).splitlines()
|
return BASE_GPL.format(name=name, version=version).splitlines()
|
||||||
elif license == OSI:
|
elif license == OSI:
|
||||||
return BASE_OSI
|
return BASE_OSI.splitlines()
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
import base64
|
import base64
|
||||||
|
import logging
|
||||||
import lxml.etree
|
import lxml.etree
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -31,10 +32,13 @@ from datetime import date
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
from openerp import models, api, fields
|
from openerp import models, api, fields
|
||||||
|
from openerp.tools.safe_eval import safe_eval
|
||||||
|
|
||||||
from .default_description import get_default_description
|
from .default_description import get_default_description
|
||||||
from . import licenses
|
from . import licenses
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ModulePrototyper(models.Model):
|
class ModulePrototyper(models.Model):
|
||||||
"""Module Prototyper gathers different information from all over the
|
"""Module Prototyper gathers different information from all over the
|
||||||
|
@ -53,7 +57,6 @@ class ModulePrototyper(models.Model):
|
||||||
(licenses.LGPL3, 'LGPL-3'),
|
(licenses.LGPL3, 'LGPL-3'),
|
||||||
(licenses.LGPL3_L, 'LGPL-3 or later version'),
|
(licenses.LGPL3_L, 'LGPL-3 or later version'),
|
||||||
(licenses.AGPL3, 'Affero GPL-3'),
|
(licenses.AGPL3, 'Affero GPL-3'),
|
||||||
(licenses.AGPL3_L, 'Affero GPL-3 or later version'),
|
|
||||||
(licenses.OSI, 'Other OSI Approved Licence'),
|
(licenses.OSI, 'Other OSI Approved Licence'),
|
||||||
('Other proprietary', 'Other Proprietary')
|
('Other proprietary', 'Other Proprietary')
|
||||||
],
|
],
|
||||||
|
@ -170,10 +173,29 @@ class ModulePrototyper(models.Model):
|
||||||
help=('Enter the list of record rules that you have created and '
|
help=('Enter the list of record rules that you have created and '
|
||||||
'want to export in this module.')
|
'want to export in this module.')
|
||||||
)
|
)
|
||||||
|
report_ids = fields.Many2many(
|
||||||
|
'ir.actions.report.xml', 'prototype_report_rel',
|
||||||
|
'module_prototyper_id', 'report_id', 'Reports',
|
||||||
|
help=('Enter the list of reports that you have created and '
|
||||||
|
'want to export in this module.')
|
||||||
|
)
|
||||||
|
activity_ids = fields.Many2many(
|
||||||
|
'workflow.activity', 'prototype_wf_activity_rel',
|
||||||
|
'module_prototyper_id', 'activity_id', 'Activities',
|
||||||
|
help=('Enter the list of workflow activities that you have created '
|
||||||
|
'and want to export in this module')
|
||||||
|
)
|
||||||
|
transition_ids = fields.Many2many(
|
||||||
|
'workflow.transition', 'prototype_wf_transition_rel',
|
||||||
|
'module_prototyper_id', 'transition_id', 'Transitions',
|
||||||
|
help=('Enter the list of workflow transitions that you have created '
|
||||||
|
'and want to export in this module')
|
||||||
|
)
|
||||||
|
|
||||||
__data_files = []
|
|
||||||
__field_descriptions = {}
|
|
||||||
_env = None
|
_env = None
|
||||||
|
_data_files = ()
|
||||||
|
_demo_files = ()
|
||||||
|
_field_descriptions = None
|
||||||
File_details = namedtuple('file_details', ['filename', 'filecontent'])
|
File_details = namedtuple('file_details', ['filename', 'filecontent'])
|
||||||
template_path = '{}/../templates/'.format(os.path.dirname(__file__))
|
template_path = '{}/../templates/'.format(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
@ -211,10 +233,8 @@ class ModulePrototyper(models.Model):
|
||||||
for attr_name in dir(field)
|
for attr_name in dir(field)
|
||||||
if not attr_name[0] == '_'
|
if not attr_name[0] == '_'
|
||||||
})
|
})
|
||||||
# custom fields start with the prefix x_.
|
field_description['name'] = self.unprefix(field.name)
|
||||||
# it has to be removed.
|
self._field_descriptions[field] = field_description
|
||||||
field_description['name'] = re.sub(r'^x_', '', field.name)
|
|
||||||
self.__field_descriptions[field] = field_description
|
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def generate_files(self):
|
def generate_files(self):
|
||||||
|
@ -224,12 +244,17 @@ class ModulePrototyper(models.Model):
|
||||||
assert self._env is not None, \
|
assert self._env is not None, \
|
||||||
'Run set_env(api_version) before to generate files.'
|
'Run set_env(api_version) before to generate files.'
|
||||||
|
|
||||||
|
# Avoid sharing these across instances
|
||||||
|
self._data_files = []
|
||||||
|
self._demo_files = []
|
||||||
|
self._field_descriptions = {}
|
||||||
self.set_field_descriptions()
|
self.set_field_descriptions()
|
||||||
file_details = []
|
file_details = []
|
||||||
file_details.extend(self.generate_models_details())
|
file_details.extend(self.generate_models_details())
|
||||||
file_details.extend(self.generate_views_details())
|
file_details.extend(self.generate_views_details())
|
||||||
file_details.extend(self.generate_menus_details())
|
file_details.extend(self.generate_menus_details())
|
||||||
file_details.append(self.generate_module_init_file_details())
|
file_details.append(self.generate_module_init_file_details())
|
||||||
|
file_details.extend(self.generate_data_files())
|
||||||
# must be the last as the other generations might add information
|
# must be the last as the other generations might add information
|
||||||
# to put in the __openerp__: additional dependencies, views files, etc.
|
# to put in the __openerp__: additional dependencies, views files, etc.
|
||||||
file_details.append(self.generate_module_openerp_file_details())
|
file_details.append(self.generate_module_openerp_file_details())
|
||||||
|
@ -262,7 +287,8 @@ class ModulePrototyper(models.Model):
|
||||||
'__openerp__.py',
|
'__openerp__.py',
|
||||||
'__openerp__.py.template',
|
'__openerp__.py.template',
|
||||||
prototype=self,
|
prototype=self,
|
||||||
data_files=self.__data_files,
|
data_files=self._data_files,
|
||||||
|
demo_fiels=self._demo_files,
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
|
@ -278,7 +304,8 @@ class ModulePrototyper(models.Model):
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def generate_models_details(self):
|
def generate_models_details(self):
|
||||||
"""Finds the models from the list of fields and generates
|
"""
|
||||||
|
Finds the models from the list of fields and generates
|
||||||
the __init__ file and each models files (one by class).
|
the __init__ file and each models files (one by class).
|
||||||
"""
|
"""
|
||||||
files = []
|
files = []
|
||||||
|
@ -291,7 +318,8 @@ class ModulePrototyper(models.Model):
|
||||||
# dependencies = set([dep.id for dep in self.dependencies])
|
# dependencies = set([dep.id for dep in self.dependencies])
|
||||||
|
|
||||||
relations = {}
|
relations = {}
|
||||||
for field in self.__field_descriptions.itervalues():
|
field_descriptions = self._field_descriptions or {}
|
||||||
|
for field in field_descriptions.itervalues():
|
||||||
model = field.get('model_id')
|
model = field.get('model_id')
|
||||||
relations.setdefault(model, []).append(field)
|
relations.setdefault(model, []).append(field)
|
||||||
# dependencies.add(model.id)
|
# dependencies.add(model.id)
|
||||||
|
@ -329,7 +357,7 @@ class ModulePrototyper(models.Model):
|
||||||
views_details = []
|
views_details = []
|
||||||
for model, views in relations.iteritems():
|
for model, views in relations.iteritems():
|
||||||
filepath = 'views/{}_view.xml'.format(
|
filepath = 'views/{}_view.xml'.format(
|
||||||
self.friendly_name(model)
|
self.friendly_name(self.unprefix(model))
|
||||||
)
|
)
|
||||||
views_details.append(
|
views_details.append(
|
||||||
self.generate_file_details(
|
self.generate_file_details(
|
||||||
|
@ -338,7 +366,7 @@ class ModulePrototyper(models.Model):
|
||||||
views=views
|
views=views
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.__data_files.append(filepath)
|
self._data_files.append(filepath)
|
||||||
|
|
||||||
return views_details
|
return views_details
|
||||||
|
|
||||||
|
@ -348,13 +376,14 @@ class ModulePrototyper(models.Model):
|
||||||
relations = {}
|
relations = {}
|
||||||
for menu in self.menu_ids:
|
for menu in self.menu_ids:
|
||||||
if menu.action and menu.action.res_model:
|
if menu.action and menu.action.res_model:
|
||||||
model = menu.action.res_model
|
model = self.unprefix(menu.action.res_model)
|
||||||
else:
|
else:
|
||||||
model = 'ir_ui'
|
model = 'ir_ui'
|
||||||
relations.setdefault(model, []).append(menu)
|
relations.setdefault(model, []).append(menu)
|
||||||
|
|
||||||
menus_details = []
|
menus_details = []
|
||||||
for model_name, menus in relations.iteritems():
|
for model_name, menus in relations.iteritems():
|
||||||
|
model_name = self.unprefix(model_name)
|
||||||
filepath = 'views/{}_menus.xml'.format(
|
filepath = 'views/{}_menus.xml'.format(
|
||||||
self.friendly_name(model_name)
|
self.friendly_name(model_name)
|
||||||
)
|
)
|
||||||
|
@ -365,7 +394,7 @@ class ModulePrototyper(models.Model):
|
||||||
menus=menus,
|
menus=menus,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.__data_files.append(filepath)
|
self._data_files.append(filepath)
|
||||||
|
|
||||||
return menus_details
|
return menus_details
|
||||||
|
|
||||||
|
@ -377,7 +406,7 @@ class ModulePrototyper(models.Model):
|
||||||
:param field_descriptions: list of ir.model.fields records.
|
:param field_descriptions: list of ir.model.fields records.
|
||||||
:return: FileDetails instance.
|
:return: FileDetails instance.
|
||||||
"""
|
"""
|
||||||
python_friendly_name = self.friendly_name(model.model)
|
python_friendly_name = self.friendly_name(self.unprefix(model.model))
|
||||||
return self.generate_file_details(
|
return self.generate_file_details(
|
||||||
'models/{}.py'.format(python_friendly_name),
|
'models/{}.py'.format(python_friendly_name),
|
||||||
'models/model_name.py.template',
|
'models/model_name.py.template',
|
||||||
|
@ -386,22 +415,87 @@ class ModulePrototyper(models.Model):
|
||||||
fields=field_descriptions,
|
fields=field_descriptions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def generate_data_files(self):
|
||||||
|
""" Generate data and demo files """
|
||||||
|
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))
|
||||||
|
|
||||||
|
res = []
|
||||||
|
for prefix, model_data, file_list in [
|
||||||
|
('data', data, self._data_files),
|
||||||
|
('demo', demo, self._demo_files)]:
|
||||||
|
for model_name, records in model_data.iteritems():
|
||||||
|
fname = self.friendly_name(self.unprefix(model_name))
|
||||||
|
filename = '{0}/{1}.xml'.format(prefix, fname)
|
||||||
|
self._data_files.append(filename)
|
||||||
|
|
||||||
|
res.append(self.generate_file_details(
|
||||||
|
filename,
|
||||||
|
'data/model_name.xml.template',
|
||||||
|
model=model_name,
|
||||||
|
records=records,
|
||||||
|
))
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unprefix(cls, name):
|
def unprefix(cls, name):
|
||||||
if not name:
|
if not name:
|
||||||
return name
|
return name
|
||||||
return re.sub('^x_', '', name)
|
return re.sub('^x_', '', name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_prefixed(cls, name):
|
||||||
|
return bool(re.match('^x_', name))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def friendly_name(cls, name):
|
def friendly_name(cls, name):
|
||||||
return name.replace('.', '_')
|
return name.replace('.', '_')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fixup_domain(cls, domain):
|
||||||
|
""" Fix a domain according to unprefixing of fields """
|
||||||
|
res = []
|
||||||
|
for elem in domain:
|
||||||
|
if len(elem) == 3:
|
||||||
|
elem = list(elem)
|
||||||
|
elem[0] = cls.unprefix(elem[0])
|
||||||
|
res.append(elem)
|
||||||
|
return res
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fixup_arch(cls, archstr):
|
def fixup_arch(cls, archstr):
|
||||||
doc = lxml.etree.fromstring(archstr)
|
doc = lxml.etree.fromstring(archstr)
|
||||||
for elem in doc.xpath("//*[@name]"):
|
for elem in doc.xpath("//*[@name]"):
|
||||||
elem.attrib["name"] = cls.unprefix(elem.attrib["name"])
|
elem.attrib["name"] = cls.unprefix(elem.attrib["name"])
|
||||||
|
|
||||||
|
for elem in doc.xpath("//*[@attrs]"):
|
||||||
|
try:
|
||||||
|
attrs = safe_eval(elem.attrib["attrs"])
|
||||||
|
except Exception:
|
||||||
|
_logger.error("Unable to eval attribute: %s, skipping",
|
||||||
|
elem.attrib["attrs"])
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(attrs, dict):
|
||||||
|
for key, val in attrs.iteritems():
|
||||||
|
if isinstance(val, (list, tuple)):
|
||||||
|
attrs[key] = cls.fixup_domain(val)
|
||||||
|
elem.attrib["attrs"] = repr(attrs)
|
||||||
|
|
||||||
for elem in doc.xpath("//field"):
|
for elem in doc.xpath("//field"):
|
||||||
# Make fields self-closed by removing useless whitespace
|
# Make fields self-closed by removing useless whitespace
|
||||||
if elem.text and not elem.text.strip():
|
if elem.text and not elem.text.strip():
|
||||||
|
@ -428,6 +522,7 @@ class ModulePrototyper(models.Model):
|
||||||
'cr': self._cr,
|
'cr': self._cr,
|
||||||
# Utility functions
|
# Utility functions
|
||||||
'fixup_arch': self.fixup_arch,
|
'fixup_arch': self.fixup_arch,
|
||||||
|
'is_prefixed': self.is_prefixed,
|
||||||
'unprefix': self.unprefix,
|
'unprefix': self.unprefix,
|
||||||
'wrap': wrap,
|
'wrap': wrap,
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,13 @@
|
||||||
'author': '{{ prototype.author }}',
|
'author': '{{ prototype.author }}',
|
||||||
'maintainer': '{{ prototype.maintainer }}',
|
'maintainer': '{{ prototype.maintainer }}',
|
||||||
'website': '{{ prototype.website }}',
|
'website': '{{ prototype.website }}',
|
||||||
'license': '{{ prototype.licence }}',
|
'license': '{{ prototype.license }}',
|
||||||
|
|
||||||
# Categories can be used to filter modules in modules listing
|
# 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
|
# Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml # noqa
|
||||||
# for the full list
|
# for the full list
|
||||||
'category': '{{ prototype.with_context({}).category_id.name }}',{# In english please! #}
|
{# Use with_context({}) to get english category #}
|
||||||
|
'category': '{{ prototype.with_context({}).category_id.name }}',
|
||||||
'summary': '{{ prototype.summary }}',
|
'summary': '{{ prototype.summary }}',
|
||||||
'description': """
|
'description': """
|
||||||
{{ prototype.description }}
|
{{ prototype.description }}
|
||||||
|
@ -40,8 +41,8 @@
|
||||||
],
|
],
|
||||||
# only loaded in demonstration mode
|
# only loaded in demonstration mode
|
||||||
'demo': [
|
'demo': [
|
||||||
{% for demo_file in prototype.demo_ids %}
|
{% for demo_file in demo_files %}
|
||||||
'{{ demo_file.name }}',
|
'{{ demo_file }}',
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
<data>
|
||||||
|
{% for record in records %}
|
||||||
|
<!--
|
||||||
|
<record id="{{ model }}_{{ loop.index }}" model="{{ model }}">
|
||||||
|
{% for key, val in record.read()[0].items() %}
|
||||||
|
<field name="{{ key }}">{{ val }}</field>
|
||||||
|
{% endfor %}
|
||||||
|
</record>
|
||||||
|
-->
|
||||||
|
{% if not loop.last %}
|
||||||
|
|
||||||
{{ data }}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
{% if loop.first %}
|
{% if loop.first %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
from . import {{ model }}
|
from . import {{ unprefix(model) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -6,10 +6,10 @@ from openerp.tools.translate import _
|
||||||
|
|
||||||
|
|
||||||
class {{ unprefix(name) }}(models.Model):
|
class {{ unprefix(name) }}(models.Model):
|
||||||
{% if model.state == 'base' %}
|
{% if model.state == 'base' and not is_prefixed(model.model) %}
|
||||||
_name = "{{ model.model }}"
|
_inherit = "{{ unprefix(model.model) }}"
|
||||||
{% else %}
|
{% else %}
|
||||||
_inherit = "{{ model.model }}"
|
_name = "{{ unprefix(model.model) }}"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if description %}
|
{% if description %}
|
||||||
_description = "{{ description }}"
|
_description = "{{ description }}"
|
||||||
|
@ -26,10 +26,13 @@ class {{ unprefix(name) }}(models.Model):
|
||||||
{{ unprefix(field.name) }} = fields.{{ field.ttype|capitalize }}(
|
{{ unprefix(field.name) }} = fields.{{ field.ttype|capitalize }}(
|
||||||
string=_("{{ field.field_description }}"),
|
string=_("{{ field.field_description }}"),
|
||||||
{% if field.selection %}
|
{% if field.selection %}
|
||||||
selection={{ selection }},
|
selection={{ field.selection }},
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if field.relation %}
|
{% if field.relation %}
|
||||||
comodel_name="{{ field.relation }}",
|
comodel_name="{{ unprefix(field.relation) }}",
|
||||||
|
{% endif %}
|
||||||
|
{% if field.ttype == 'one2many' %}
|
||||||
|
inverse_name="{{ unprefix(field.relation_field) }}",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if field.column1 %}
|
{% if field.column1 %}
|
||||||
column1="{{ field.column1 }}",
|
column1="{{ field.column1 }}",
|
||||||
|
@ -43,11 +46,13 @@ class {{ unprefix(name) }}(models.Model):
|
||||||
{% if field.size %}
|
{% if field.size %}
|
||||||
size={{ field.size }},
|
size={{ field.size }},
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if field.domain %}
|
{% if field.ttype in ('many2one', 'many2many', 'one2many') %}
|
||||||
|
{% if field.domain %}
|
||||||
domain={{ field.domain }},
|
domain={{ field.domain }},
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if field.context %}
|
{% if field.client_context %}
|
||||||
context={{ field.context }},
|
context={{ field.client_context }},
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if field.limit %}
|
{% if field.limit %}
|
||||||
limit={{ field.limit }},
|
limit={{ field.limit }},
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
id="menu_action_{{ unprefix(menu.name)|replace('.', '_') }}_{{ menu.action.view_type }}"
|
id="menu_action_{{ unprefix(menu.name)|replace('.', '_') }}_{{ menu.action.view_type }}"
|
||||||
{% if menu.parent_id %}parent="{{ menu.parent_id.get_xml_id(cr,1,1).values()[0] }}"{% endif %}
|
{% if menu.parent_id %}parent="{{ menu.parent_id.get_xml_id(cr,1,1).values()[0] }}"{% endif %}
|
||||||
sequence="{{ menu.sequence }}"
|
sequence="{{ menu.sequence }}"
|
||||||
groups="{% for group in menu.groups_id %}{{ group.get_xml_id(cr,1,1).values()[0] }},{% endfor %}"
|
groups="{% for group in menu.groups_id %}{{ group.get_xml_id(cr,1,1).values()[0] }}{% if not loop.last %},{% endif %}{% endfor %}"
|
||||||
/>
|
/>
|
||||||
{% if not loop.last %}
|
{% if not loop.last %}
|
||||||
|
|
||||||
|
|
|
@ -69,10 +69,7 @@ class test_prototype_module_export(common.TransactionCase):
|
||||||
|
|
||||||
def test_zip_files_returns_tuple(self):
|
def test_zip_files_returns_tuple(self):
|
||||||
"""Test the method return of the method that generate the zip file."""
|
"""Test the method return of the method that generate the zip file."""
|
||||||
file_details = (
|
ret = self.main_model.zip_files(self.exporter, [self.prototype])
|
||||||
('test.txt', 'generated'),
|
|
||||||
)
|
|
||||||
ret = self.main_model.zip_files(file_details)
|
|
||||||
self.assertIsInstance(ret, tuple)
|
self.assertIsInstance(ret, tuple)
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
ret.zip_file, zipfile.ZipFile
|
ret.zip_file, zipfile.ZipFile
|
||||||
|
|
|
@ -27,8 +27,8 @@
|
||||||
<field name="limit"
|
<field name="limit"
|
||||||
attrs="{'invisible': [('ttype', '!=', 'many2many')]}"
|
attrs="{'invisible': [('ttype', '!=', 'many2many')]}"
|
||||||
/>
|
/>
|
||||||
<field name="context"
|
<field name="client_context"
|
||||||
attrs="{'invisible': [('type', 'not in', ['many2one','one2many','many2many'])]}"
|
attrs="{'invisible': [('ttype', 'not in', ['many2one','one2many','many2many'])]}"
|
||||||
/>
|
/>
|
||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
@ -61,13 +61,6 @@
|
||||||
<page string="Dependencies">
|
<page string="Dependencies">
|
||||||
<field name="dependency_ids"/>
|
<field name="dependency_ids"/>
|
||||||
</page>
|
</page>
|
||||||
<!--Not implemented yet-->
|
|
||||||
<!--<page string="Data & Demo">-->
|
|
||||||
<!--<label for="data_ids"/>-->
|
|
||||||
<!--<field name="data_ids"/>-->
|
|
||||||
<!--<label for="demo_ids"/>-->
|
|
||||||
<!--<field name="demo_ids"/>-->
|
|
||||||
<!--</page>-->
|
|
||||||
<page string="Fields">
|
<page string="Fields">
|
||||||
<label for="field_ids"/>
|
<label for="field_ids"/>
|
||||||
<field name="field_ids"/>
|
<field name="field_ids"/>
|
||||||
|
@ -78,15 +71,30 @@
|
||||||
<label for="menu_ids"/>
|
<label for="menu_ids"/>
|
||||||
<field name="menu_ids"/>
|
<field name="menu_ids"/>
|
||||||
</page>
|
</page>
|
||||||
<!--Not implemented yet-->
|
<page string="Data & Demo">
|
||||||
<!--<page string="Security">-->
|
<label for="data_ids"/>
|
||||||
<!--<label for="group_ids"/>-->
|
<field name="data_ids"/>
|
||||||
<!--<field name="group_ids"/>-->
|
<label for="demo_ids"/>
|
||||||
<!--<label for="right_ids"/>-->
|
<field name="demo_ids"/>
|
||||||
<!--<field name="right_ids"/>-->
|
</page>
|
||||||
<!--<label for="rule_ids"/>-->
|
<page string="Reports">
|
||||||
<!--<field name="rule_ids"/>-->
|
<label for="report_ids" />
|
||||||
<!--</page>-->
|
<field name="report_ids" />
|
||||||
|
</page>
|
||||||
|
<page string="Security">
|
||||||
|
<label for="group_ids"/>
|
||||||
|
<field name="group_ids"/>
|
||||||
|
<label for="right_ids"/>
|
||||||
|
<field name="right_ids"/>
|
||||||
|
<label for="rule_ids"/>
|
||||||
|
<field name="rule_ids"/>
|
||||||
|
</page>
|
||||||
|
<page string="Workflows">
|
||||||
|
<label for="activity_ids" />
|
||||||
|
<field name="activity_ids" />
|
||||||
|
<label for="transition_ids" />
|
||||||
|
<field name="transition_ids" />
|
||||||
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -19,10 +19,13 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
import StringIO
|
import StringIO
|
||||||
import base64
|
import base64
|
||||||
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from openerp import fields, models, api
|
from openerp import fields, models, api
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,24 +69,20 @@ class PrototypeModuleExport(models.TransientModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
# getting the prototype of the wizard
|
# getting the prototype of the wizard
|
||||||
prototype = self.env[active_model].browse(
|
prototypes = self.env[active_model].browse(
|
||||||
self._context.get('active_id')
|
[self._context.get('active_id')]
|
||||||
)
|
)
|
||||||
|
|
||||||
# setting the jinja environment.
|
zip_details = self.zip_files(wizard, prototypes)
|
||||||
# They will help the program to find the template to render the files
|
|
||||||
# with.
|
|
||||||
prototype.set_jinja_env(wizard.api_version)
|
|
||||||
|
|
||||||
# generate_files ask the prototype to investigate the input
|
if len(prototypes) == 1:
|
||||||
# and to generate the file templates according to it.
|
zip_name = prototypes[0].name
|
||||||
# zip_files, in another hand, put all the template files into a package
|
else:
|
||||||
# ready to be saved by the user.
|
zip_name = "prototyper_export"
|
||||||
zip_details = self.zip_files(prototype.generate_files())
|
|
||||||
|
|
||||||
wizard.write(
|
wizard.write(
|
||||||
{
|
{
|
||||||
'name': '{}.zip'.format(prototype.name),
|
'name': '{}.zip'.format(zip_name),
|
||||||
'state': 'get',
|
'state': 'get',
|
||||||
'data': base64.encodestring(zip_details.stringIO.getvalue())
|
'data': base64.encodestring(zip_details.stringIO.getvalue())
|
||||||
}
|
}
|
||||||
|
@ -100,7 +99,7 @@ class PrototypeModuleExport(models.TransientModel):
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def zip_files(file_details):
|
def zip_files(wizard, prototypes):
|
||||||
"""Takes a set of file and zips them.
|
"""Takes a set of file and zips them.
|
||||||
:param file_details: tuple (filename, file_content)
|
:param file_details: tuple (filename, file_content)
|
||||||
:return: tuple (zip_file, stringIO)
|
:return: tuple (zip_file, stringIO)
|
||||||
|
@ -109,10 +108,25 @@ class PrototypeModuleExport(models.TransientModel):
|
||||||
out = StringIO.StringIO()
|
out = StringIO.StringIO()
|
||||||
|
|
||||||
with zipfile.ZipFile(out, 'w') as target:
|
with zipfile.ZipFile(out, 'w') as target:
|
||||||
for filename, file_content in file_details:
|
for prototype in prototypes:
|
||||||
info = zipfile.ZipInfo(filename)
|
# setting the jinja environment.
|
||||||
info.compress_type = zipfile.ZIP_DEFLATED
|
# They will help the program to find the template to render the
|
||||||
info.external_attr = 2175008768 # specifies mode 0644
|
# files with.
|
||||||
target.writestr(info, file_content)
|
prototype.set_jinja_env(wizard.api_version)
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
for filename, file_content in file_details:
|
||||||
|
if isinstance(file_content, unicode):
|
||||||
|
file_content = file_content.encode('utf-8')
|
||||||
|
# Prefix all names with module technical name
|
||||||
|
filename = os.path.join(prototype.name, filename)
|
||||||
|
info = zipfile.ZipInfo(filename)
|
||||||
|
info.compress_type = zipfile.ZIP_DEFLATED
|
||||||
|
info.external_attr = 2175008768 # specifies mode 0644
|
||||||
|
target.writestr(info, file_content)
|
||||||
|
|
||||||
return zip_details(zip_file=target, stringIO=out)
|
return zip_details(zip_file=target, stringIO=out)
|
||||||
|
|
Loading…
Reference in New Issue