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>
|
||||
* El hadji Dem <elhadji.dem@savoirfairelinux.com>
|
||||
* Savoir-faire Linux <support@savoirfairelinux.com>
|
||||
* Vincent Vinet <vincent.vinet@savoirfairelinux.com>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
|
|
@ -42,7 +42,7 @@ class ir_model_fields(models.Model):
|
|||
"relation table"),
|
||||
)
|
||||
limit = fields.Integer('Read limit', help=_("Read limit"))
|
||||
context = fields.Char(
|
||||
client_context = fields.Char(
|
||||
'Context',
|
||||
help=_("Context to use on the client side when handling the field "
|
||||
"(python dictionary)"),
|
||||
|
|
|
@ -68,6 +68,6 @@ def get_license_text(license):
|
|||
name, version = GPL_LICENSES[license]
|
||||
return BASE_GPL.format(name=name, version=version).splitlines()
|
||||
elif license == OSI:
|
||||
return BASE_OSI
|
||||
return BASE_OSI.splitlines()
|
||||
else:
|
||||
return ""
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
import base64
|
||||
import logging
|
||||
import lxml.etree
|
||||
import os
|
||||
import re
|
||||
|
@ -31,10 +32,13 @@ from datetime import date
|
|||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from openerp import models, api, fields
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
|
||||
from .default_description import get_default_description
|
||||
from . import licenses
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ModulePrototyper(models.Model):
|
||||
"""Module Prototyper gathers different information from all over the
|
||||
|
@ -53,7 +57,6 @@ class ModulePrototyper(models.Model):
|
|||
(licenses.LGPL3, 'LGPL-3'),
|
||||
(licenses.LGPL3_L, 'LGPL-3 or later version'),
|
||||
(licenses.AGPL3, 'Affero GPL-3'),
|
||||
(licenses.AGPL3_L, 'Affero GPL-3 or later version'),
|
||||
(licenses.OSI, 'Other OSI Approved Licence'),
|
||||
('Other proprietary', 'Other Proprietary')
|
||||
],
|
||||
|
@ -170,10 +173,29 @@ class ModulePrototyper(models.Model):
|
|||
help=('Enter the list of record rules that you have created and '
|
||||
'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
|
||||
_data_files = ()
|
||||
_demo_files = ()
|
||||
_field_descriptions = None
|
||||
File_details = namedtuple('file_details', ['filename', 'filecontent'])
|
||||
template_path = '{}/../templates/'.format(os.path.dirname(__file__))
|
||||
|
||||
|
@ -211,10 +233,8 @@ class ModulePrototyper(models.Model):
|
|||
for attr_name in dir(field)
|
||||
if not attr_name[0] == '_'
|
||||
})
|
||||
# custom fields start with the prefix x_.
|
||||
# it has to be removed.
|
||||
field_description['name'] = re.sub(r'^x_', '', field.name)
|
||||
self.__field_descriptions[field] = field_description
|
||||
field_description['name'] = self.unprefix(field.name)
|
||||
self._field_descriptions[field] = field_description
|
||||
|
||||
@api.model
|
||||
def generate_files(self):
|
||||
|
@ -224,12 +244,17 @@ class ModulePrototyper(models.Model):
|
|||
assert self._env is not None, \
|
||||
'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()
|
||||
file_details = []
|
||||
file_details.extend(self.generate_models_details())
|
||||
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())
|
||||
# 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())
|
||||
|
@ -262,7 +287,8 @@ class ModulePrototyper(models.Model):
|
|||
'__openerp__.py',
|
||||
'__openerp__.py.template',
|
||||
prototype=self,
|
||||
data_files=self.__data_files,
|
||||
data_files=self._data_files,
|
||||
demo_fiels=self._demo_files,
|
||||
)
|
||||
|
||||
@api.model
|
||||
|
@ -278,7 +304,8 @@ class ModulePrototyper(models.Model):
|
|||
|
||||
@api.model
|
||||
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).
|
||||
"""
|
||||
files = []
|
||||
|
@ -291,7 +318,8 @@ class ModulePrototyper(models.Model):
|
|||
# dependencies = set([dep.id for dep in self.dependencies])
|
||||
|
||||
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')
|
||||
relations.setdefault(model, []).append(field)
|
||||
# dependencies.add(model.id)
|
||||
|
@ -329,7 +357,7 @@ class ModulePrototyper(models.Model):
|
|||
views_details = []
|
||||
for model, views in relations.iteritems():
|
||||
filepath = 'views/{}_view.xml'.format(
|
||||
self.friendly_name(model)
|
||||
self.friendly_name(self.unprefix(model))
|
||||
)
|
||||
views_details.append(
|
||||
self.generate_file_details(
|
||||
|
@ -338,7 +366,7 @@ class ModulePrototyper(models.Model):
|
|||
views=views
|
||||
)
|
||||
)
|
||||
self.__data_files.append(filepath)
|
||||
self._data_files.append(filepath)
|
||||
|
||||
return views_details
|
||||
|
||||
|
@ -348,13 +376,14 @@ class ModulePrototyper(models.Model):
|
|||
relations = {}
|
||||
for menu in self.menu_ids:
|
||||
if menu.action and menu.action.res_model:
|
||||
model = menu.action.res_model
|
||||
model = self.unprefix(menu.action.res_model)
|
||||
else:
|
||||
model = 'ir_ui'
|
||||
relations.setdefault(model, []).append(menu)
|
||||
|
||||
menus_details = []
|
||||
for model_name, menus in relations.iteritems():
|
||||
model_name = self.unprefix(model_name)
|
||||
filepath = 'views/{}_menus.xml'.format(
|
||||
self.friendly_name(model_name)
|
||||
)
|
||||
|
@ -365,7 +394,7 @@ class ModulePrototyper(models.Model):
|
|||
menus=menus,
|
||||
)
|
||||
)
|
||||
self.__data_files.append(filepath)
|
||||
self._data_files.append(filepath)
|
||||
|
||||
return menus_details
|
||||
|
||||
|
@ -377,7 +406,7 @@ class ModulePrototyper(models.Model):
|
|||
:param field_descriptions: list of ir.model.fields records.
|
||||
: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(
|
||||
'models/{}.py'.format(python_friendly_name),
|
||||
'models/model_name.py.template',
|
||||
|
@ -386,22 +415,87 @@ class ModulePrototyper(models.Model):
|
|||
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
|
||||
def unprefix(cls, name):
|
||||
if not name:
|
||||
return name
|
||||
return re.sub('^x_', '', name)
|
||||
|
||||
@classmethod
|
||||
def is_prefixed(cls, name):
|
||||
return bool(re.match('^x_', name))
|
||||
|
||||
@classmethod
|
||||
def friendly_name(cls, name):
|
||||
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
|
||||
def fixup_arch(cls, archstr):
|
||||
doc = lxml.etree.fromstring(archstr)
|
||||
for elem in doc.xpath("//*[@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"):
|
||||
# Make fields self-closed by removing useless whitespace
|
||||
if elem.text and not elem.text.strip():
|
||||
|
@ -428,6 +522,7 @@ class ModulePrototyper(models.Model):
|
|||
'cr': self._cr,
|
||||
# Utility functions
|
||||
'fixup_arch': self.fixup_arch,
|
||||
'is_prefixed': self.is_prefixed,
|
||||
'unprefix': self.unprefix,
|
||||
'wrap': wrap,
|
||||
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
'author': '{{ prototype.author }}',
|
||||
'maintainer': '{{ prototype.maintainer }}',
|
||||
'website': '{{ prototype.website }}',
|
||||
'license': '{{ prototype.licence }}',
|
||||
'license': '{{ prototype.license }}',
|
||||
|
||||
# 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! #}
|
||||
{# Use with_context({}) to get english category #}
|
||||
'category': '{{ prototype.with_context({}).category_id.name }}',
|
||||
'summary': '{{ prototype.summary }}',
|
||||
'description': """
|
||||
{{ prototype.description }}
|
||||
|
@ -40,8 +41,8 @@
|
|||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
{% for demo_file in prototype.demo_ids %}
|
||||
'{{ demo_file.name }}',
|
||||
{% for demo_file in demo_files %}
|
||||
'{{ demo_file }}',
|
||||
{% endfor %}
|
||||
],
|
||||
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<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>
|
||||
</openerp>
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
{% if loop.first %}
|
||||
|
||||
{% endif %}
|
||||
from . import {{ model }}
|
||||
from . import {{ unprefix(model) }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -6,10 +6,10 @@ from openerp.tools.translate import _
|
|||
|
||||
|
||||
class {{ unprefix(name) }}(models.Model):
|
||||
{% if model.state == 'base' %}
|
||||
_name = "{{ model.model }}"
|
||||
{% if model.state == 'base' and not is_prefixed(model.model) %}
|
||||
_inherit = "{{ unprefix(model.model) }}"
|
||||
{% else %}
|
||||
_inherit = "{{ model.model }}"
|
||||
_name = "{{ unprefix(model.model) }}"
|
||||
{% endif %}
|
||||
{% if description %}
|
||||
_description = "{{ description }}"
|
||||
|
@ -26,10 +26,13 @@ class {{ unprefix(name) }}(models.Model):
|
|||
{{ unprefix(field.name) }} = fields.{{ field.ttype|capitalize }}(
|
||||
string=_("{{ field.field_description }}"),
|
||||
{% if field.selection %}
|
||||
selection={{ selection }},
|
||||
selection={{ field.selection }},
|
||||
{% endif %}
|
||||
{% if field.relation %}
|
||||
comodel_name="{{ field.relation }}",
|
||||
comodel_name="{{ unprefix(field.relation) }}",
|
||||
{% endif %}
|
||||
{% if field.ttype == 'one2many' %}
|
||||
inverse_name="{{ unprefix(field.relation_field) }}",
|
||||
{% endif %}
|
||||
{% if field.column1 %}
|
||||
column1="{{ field.column1 }}",
|
||||
|
@ -43,11 +46,13 @@ class {{ unprefix(name) }}(models.Model):
|
|||
{% if field.size %}
|
||||
size={{ field.size }},
|
||||
{% endif %}
|
||||
{% if field.ttype in ('many2one', 'many2many', 'one2many') %}
|
||||
{% if field.domain %}
|
||||
domain={{ field.domain }},
|
||||
{% endif %}
|
||||
{% if field.context %}
|
||||
context={{ field.context }},
|
||||
{% if field.client_context %}
|
||||
context={{ field.client_context }},
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if field.limit %}
|
||||
limit={{ field.limit }},
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
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 %}
|
||||
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 %}
|
||||
|
||||
|
|
|
@ -69,10 +69,7 @@ class test_prototype_module_export(common.TransactionCase):
|
|||
|
||||
def test_zip_files_returns_tuple(self):
|
||||
"""Test the method return of the method that generate the zip file."""
|
||||
file_details = (
|
||||
('test.txt', 'generated'),
|
||||
)
|
||||
ret = self.main_model.zip_files(file_details)
|
||||
ret = self.main_model.zip_files(self.exporter, [self.prototype])
|
||||
self.assertIsInstance(ret, tuple)
|
||||
self.assertIsInstance(
|
||||
ret.zip_file, zipfile.ZipFile
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
<field name="limit"
|
||||
attrs="{'invisible': [('ttype', '!=', 'many2many')]}"
|
||||
/>
|
||||
<field name="context"
|
||||
attrs="{'invisible': [('type', 'not in', ['many2one','one2many','many2many'])]}"
|
||||
<field name="client_context"
|
||||
attrs="{'invisible': [('ttype', 'not in', ['many2one','one2many','many2many'])]}"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
|
|
|
@ -61,13 +61,6 @@
|
|||
<page string="Dependencies">
|
||||
<field name="dependency_ids"/>
|
||||
</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">
|
||||
<label for="field_ids"/>
|
||||
<field name="field_ids"/>
|
||||
|
@ -78,15 +71,30 @@
|
|||
<label for="menu_ids"/>
|
||||
<field name="menu_ids"/>
|
||||
</page>
|
||||
<!--Not implemented yet-->
|
||||
<!--<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="Data & Demo">
|
||||
<label for="data_ids"/>
|
||||
<field name="data_ids"/>
|
||||
<label for="demo_ids"/>
|
||||
<field name="demo_ids"/>
|
||||
</page>
|
||||
<page string="Reports">
|
||||
<label for="report_ids" />
|
||||
<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>
|
||||
</sheet>
|
||||
</form>
|
||||
|
|
|
@ -19,10 +19,13 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import StringIO
|
||||
import base64
|
||||
import os
|
||||
import zipfile
|
||||
from collections import namedtuple
|
||||
|
||||
from openerp import fields, models, api
|
||||
|
||||
|
||||
|
@ -66,24 +69,20 @@ class PrototypeModuleExport(models.TransientModel):
|
|||
)
|
||||
|
||||
# getting the prototype of the wizard
|
||||
prototype = self.env[active_model].browse(
|
||||
self._context.get('active_id')
|
||||
prototypes = self.env[active_model].browse(
|
||||
[self._context.get('active_id')]
|
||||
)
|
||||
|
||||
# setting the jinja environment.
|
||||
# They will help the program to find the template to render the files
|
||||
# with.
|
||||
prototype.set_jinja_env(wizard.api_version)
|
||||
zip_details = self.zip_files(wizard, prototypes)
|
||||
|
||||
# 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.
|
||||
zip_details = self.zip_files(prototype.generate_files())
|
||||
if len(prototypes) == 1:
|
||||
zip_name = prototypes[0].name
|
||||
else:
|
||||
zip_name = "prototyper_export"
|
||||
|
||||
wizard.write(
|
||||
{
|
||||
'name': '{}.zip'.format(prototype.name),
|
||||
'name': '{}.zip'.format(zip_name),
|
||||
'state': 'get',
|
||||
'data': base64.encodestring(zip_details.stringIO.getvalue())
|
||||
}
|
||||
|
@ -100,7 +99,7 @@ class PrototypeModuleExport(models.TransientModel):
|
|||
}
|
||||
|
||||
@staticmethod
|
||||
def zip_files(file_details):
|
||||
def zip_files(wizard, prototypes):
|
||||
"""Takes a set of file and zips them.
|
||||
:param file_details: tuple (filename, file_content)
|
||||
:return: tuple (zip_file, stringIO)
|
||||
|
@ -109,7 +108,22 @@ class PrototypeModuleExport(models.TransientModel):
|
|||
out = StringIO.StringIO()
|
||||
|
||||
with zipfile.ZipFile(out, 'w') as target:
|
||||
for prototype in prototypes:
|
||||
# setting the jinja environment.
|
||||
# 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 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
|
||||
|
|
Loading…
Reference in New Issue