base_module_doc_rst/wizard/tech_rst_guide.py
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
from openerp import netsvc
import base64
import tempfile
import tarfile
import httplib
import os
class RstDoc(object):
def __init__(self, module, objects):
self.dico = {
'name': module.name,
'shortdesc': module.shortdesc,
'latest_version': module.latest_version,
'website': module.website,
'description': self._handle_text(
module.description.strip() or 'None'
),
'report_list': self._handle_list_items(module.reports_by_module),
'menu_list': self._handle_list_items(module.menus_by_module),
'view_list': self._handle_list_items(module.views_by_module),
'depends': module.dependencies_id,
'author': module.author,
}
self.objects = objects
self.module = module
def _handle_list_items(self, list_item_as_string):
list_item_as_string = list_item_as_string.strip()
if list_item_as_string:
return [
item.replace("*", r"\*") for
item in
list_item_as_string.split('\n')
]
else:
return []
def _handle_text(self, txt):
lst = [' %s' % line for line in txt.split('\n')]
return '\n'.join(lst)
def _get_download_links(self):
def _is_connection_status_good(link):
server = "openerp.com"
status_good = False
try:
conn = httplib.HTTPConnection(server)
conn.request("HEAD", link)
res = conn.getresponse()
if res.status in (200, ):
status_good = True
except (Exception, ), e:
logger = netsvc.Logger()
msg = """
error connecting to server '%s' with link '%s'.
Error message: %s
""" % (server, link, str(e))
logger.notifyChannel(
"base_module_doc_rst", netsvc.LOG_ERROR, msg
)
status_good = False
return status_good
versions = ('7.0', '8.0', 'master')
download_links = []
for ver in versions:
link = 'https://apps.odoo.com/loempia/download/%s/%s.zip' % (
ver, self.dico['name']
)
if _is_connection_status_good(link):
download_links.append(" * `%s <%s>`_" % (ver, link))
if download_links:
res = '\n'.join(download_links)
else:
res = "(No download links available)"
return res
def _write_header(self):
dico = self.dico
title = "%s (*%s*)" % (dico['shortdesc'], dico['name'])
title_underline = "=" * len(title)
dico['title'] = title
dico['title_underline'] = title_underline
dico['download_links'] = self._get_download_links()
sl = [
"",
".. module:: %(name)s",
" :synopsis: %(shortdesc)s",
" :noindex:",
".. ",
"",
".. raw:: html",
"",
" <br />",
"""
<link rel="stylesheet"
href="../_static/hide_objects_in_sidebar.css"
type="text/css" />
""",
"",
"",
".. raw:: html",
"",
"""
<div class="js-kit-rating"
title="" permalink="" standalone="yes" path="/%s"></div>
""" % (dico['name'],),
""" <script src="http://js-kit.com/ratings.js"></script>""",
"",
"%(title)s",
"%(title_underline)s",
":Module: %(name)s",
":Name: %(shortdesc)s",
":Version: %(latest_version)s",
":Author: %(author)s",
":Directory: %(name)s",
":Web: %(website)s",
"",
"Description",
"-----------",
"",
"::",
"",
"%(description)s",
"",
"Download links",
"--------------",
"",
"You can download this module as a zip file in following version:",
"",
"%(download_links)s",
"",
""]
return '\n'.join(sl) % (dico)
def _write_reports(self):
sl = ["",
"Reports",
"-------"]
reports = self.dico['report_list']
if reports:
for report in reports:
if report:
sl.append("")
sl.append(" * %s" % report)
else:
sl.extend(["", "None", ""])
sl.append("")
return '\n'.join(sl)
def _write_menus(self):
sl = ["",
"Menus",
"-------",
""]
menus = self.dico['menu_list']
if menus:
for menu in menus:
if menu:
sl.append(" * %s" % menu)
else:
sl.extend(["", "None", ""])
sl.append("")
return '\n'.join(sl)
def _write_views(self):
sl = ["",
"Views",
"-----",
""]
views = self.dico['view_list']
if views:
for view in views:
if view:
sl.append(" * %s" % view)
else:
sl.extend(["", "None", ""])
sl.append("")
return '\n'.join(sl)
def _write_depends(self):
sl = ["",
"Dependencies",
"------------",
""]
depends = self.dico['depends']
if depends:
for dependency in depends:
sl.append(" * :mod:`%s`" % (dependency.name))
else:
sl.extend(["", "None", ""])
sl.append("")
return '\n'.join(sl)
def _write_objects(self):
def write_field(field_def):
if not isinstance(field_def, tuple):
logger = netsvc.Logger()
msg = "Error on Object %s: field_def: %s [type: %s]" % (
obj_name.encode('utf8'),
field_def.encode('utf8'),
type(field_def)
)
logger.notifyChannel(
"base_module_doc_rst", netsvc.LOG_ERROR, msg
)
return ""
field_name = field_def[0]
field_dict = field_def[1]
field_required = field_dict.get('required', '') and ', required'
field_readonly = field_dict.get('readonly', '') and ', readonly'
field_help_s = field_dict.get('help', '')
if field_help_s:
field_help_s = "*%s*" % (field_help_s)
field_help = '\n'.join(
[
' %s' % line.strip() for
line in
field_help_s.split('\n')
]
)
else:
field_help = ''
sl = [
"",
":%s: %s, %s%s%s" % (field_name,
field_dict.get('string', 'Unknown'),
field_dict['type'],
field_required,
field_readonly),
"",
field_help,
]
return '\n'.join(sl)
sl = ["",
"",
"Objects",
"-------"]
if self.objects:
for obj in self.objects:
obj_name = obj['object'].name
obj_model = obj['object'].model
title = "Object: %s (%s)" % (obj_name, obj_model)
slo = [
"",
title,
'#' * len(title),
"",
]
for field in obj['fields']:
slf = [
"",
write_field(field),
"",
]
slo.extend(slf)
sl.extend(slo)
else:
sl.extend(["", "None", ""])
return u'\n'.join([a.decode('utf8') for a in sl])
def _write_relationship_graph(self, module_name=False):
sl = ["",
"Relationship Graph",
"------------------",
"",
".. figure:: %s_module.png" % (module_name, ),
" :scale: 50",
" :align: center",
""]
sl.append("")
return '\n'.join(sl)
def write(self, module_name=False):
s = ''
s += self._write_header()
s += self._write_depends()
s += self._write_reports()
s += self._write_menus()
s += self._write_views()
s += self._write_objects()
if module_name:
s += self._write_relationship_graph(module_name)
return s
class WizardTechGuideRst(orm.TransientModel):
_name = "tech.guide.rst"
_columns = {
'rst_file': fields.binary('File', required=True, readonly=True),
}
def _generate(self, cr, uid, context):
module_model = self.pool.get('ir.module.module')
module_ids = context['active_ids']
module_index = []
# create a temporary gzipped tarfile:
tgz_tmp_filename = tempfile.mktemp('_rst_module_doc.tgz')
try:
tarf = tarfile.open(tgz_tmp_filename, 'w:gz')
modules = module_model.browse(cr, uid, module_ids)
for module in modules:
index_dict = {
'name': module.name,
'shortdesc': module.shortdesc,
}
module_index.append(index_dict)
objects = self._get_objects(cr, uid, module)
module.test_views = self._get_views(
cr, uid, module.id, context=context
)
rstdoc = RstDoc(module, objects)
# Append Relationship Graph on rst
graph_mod = False
module_name = False
if module.file_graph:
graph_mod = base64.decodestring(module.file_graph)
else:
module_data = module_model.get_relation_graph(
cr, uid, module.name, context=context
)
if module_data['module_file']:
graph_mod = base64.decodestring(
module_data['module_file']
)
if graph_mod:
module_name = module.name
try:
tmp_file_graph = tempfile.NamedTemporaryFile()
tmp_file_graph.write(graph_mod)
tmp_file_graph.file.flush()
tarf.add(
tmp_file_graph.name,
arcname=module.name + '_module.png'
)
finally:
tmp_file_graph.close()
out = rstdoc.write(module_name)
try:
tmp_file = tempfile.NamedTemporaryFile()
tmp_file.write(out.encode('utf8'))
tmp_file.file.flush() # write content to file
tarf.add(tmp_file.name, arcname=module.name + '.rst')
finally:
tmp_file.close()
# write index file:
tmp_file = tempfile.NamedTemporaryFile()
out = self._create_index(module_index)
tmp_file.write(out.encode('utf8'))
tmp_file.file.flush()
tarf.add(tmp_file.name, arcname='index.rst')
finally:
tarf.close()
f = open(tgz_tmp_filename, 'rb')
out = f.read()
f.close()
if os.path.exists(tgz_tmp_filename):
try:
os.unlink(tgz_tmp_filename)
except Exception, e:
logger = netsvc.Logger()
msg = "Temporary file %s could not be deleted. (%s)" % (
tgz_tmp_filename, e
)
logger.notifyChannel("warning", netsvc.LOG_WARNING, msg)
return base64.encodestring(out)
def _get_views(self, cr, uid, module_id, context=None):
module_module_obj = self.pool.get('ir.module.module')
model_data_obj = self.pool.get('ir.model.data')
view_obj = self.pool.get('ir.ui.view')
report_obj = self.pool.get('ir.actions.report.xml')
menu_obj = self.pool.get('ir.ui.menu')
res = {}
mlist = module_module_obj.browse(cr, uid, [module_id], context=context)
mnames = {}
for m in mlist:
mnames[m.name] = m.id
res[m.id] = {
'menus_by_module': [],
'reports_by_module': [],
'views_by_module': []
}
view_id = model_data_obj.search(
cr,
uid,
[
('module', 'in', mnames.keys()),
('model', 'in', (
'ir.ui.view', 'ir.actions.report.xml', 'ir.ui.menu'
))
]
)
for data_id in model_data_obj.browse(cr, uid, view_id, context):
# We use try except, because views or menus may not exist
try:
key = data_id['model']
if key == 'ir.ui.view':
v = view_obj.browse(cr, uid, data_id.res_id)
v_dict = {
'name': v.name,
'inherit': v.inherit_id,
'type': v.type}
res[mnames[data_id.module]]['views_by_module'].append(
v_dict
)
elif key == 'ir.actions.report.xml':
res[mnames[data_id.module]]['reports_by_module'].append(
report_obj.browse(cr, uid, data_id.res_id).name
)
elif key == 'ir.ui.menu':
res[mnames[data_id.module]]['menus_by_module'].append(
menu_obj.browse(cr, uid, data_id.res_id).complete_name
)
except (KeyError, ):
pass
return res
def _create_index(self, module_index):
sl = ["",
".. _module-technical-guide-link:",
"",
"Module Technical Guide: Introspection report on objects",
"=======================================================",
"",
".. toctree::",
" :maxdepth: 1",
"",
]
for mod in module_index:
sl.append(" %s" % mod['name'])
sl.append("")
return '\n'.join(sl)
def _get_objects(self, cr, uid, module):
res = []
objects = self._object_find(cr, uid, module)
for obj in objects:
fields = self._fields_find(cr, uid, obj.model)
dico = {
'object': obj,
'fields': fields
}
res.append(dico)
return res
def _object_find(self, cr, uid, module):
ir_model_data = self.pool.get('ir.model.data')
ids2 = ir_model_data.search(
cr, uid, [('module', '=', module.name), ('model', '=', 'ir.model')]
)
ids = []
for mod in ir_model_data.browse(cr, uid, ids2):
ids.append(mod.res_id)
return self.pool.get('ir.model').browse(cr, uid, ids)
def _fields_find(self, cr, uid, obj):
modobj = self.pool.get(obj)
if modobj:
res = modobj.fields_get(cr, uid).items()
return res
else:
logger = netsvc.Logger()
msg = "Object %s not found" % (obj)
logger.notifyChannel("base_module_doc_rst", netsvc.LOG_ERROR, msg)
return ""
_defaults = {
'rst_file': _generate,
}