1
# -*- encoding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as
9
# published by the Free Software Foundation, either version 3 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
17
# You should have received a copy of the GNU Affero General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
##############################################################################
21
from os.path import join
33
import base_module_doc_rst
36
choose_file_form = '''<?xml version="1.0"?>
37
<form string="Create Technical Guide in rst format">
38
<separator string="Technical Guide in rst format" colspan="4"/>
39
<label string="Please choose a file where the Technical Guide will be written." colspan="4"/>
40
<field name="rst_file" />
41
<field name="name" invisible="1"/>
45
choose_file_fields = {
46
'rst_file': {'string': 'file', 'type': 'binary', 'required': True, 'readonly': True},
47
'name': {'string': 'filename', 'type': 'char', 'required': True, 'readonly': True},
51
def __init__(self, module, objects):
54
'shortdesc': module.shortdesc,
55
'latest_version': module.latest_version,
56
'website': module.website,
57
'description': self._handle_text(module.description.strip() or 'None'),
58
'report_list': self._handle_list_items(module.reports_by_module),
59
'menu_list': self._handle_list_items(module.menus_by_module),
60
'view_list': self._handle_list_items(module.views_by_module),
61
'depends': module.dependencies_id,
62
'quality_certified': bool(module.certificate) and 'yes' or 'no',
63
'official_module': str(module.certificate)[:2] == '00' and 'yes' or 'no',
64
'author': module.author,
65
'quality_certified_label': self._quality_certified_label(module),
67
self.objects = objects
70
def _quality_certified_label(self, module):
72
certificate = module.certificate
73
if certificate and len(certificate) > 1:
74
if certificate[:2] == '00':
76
label = "(Official, Quality Certified)"
77
elif certificate[:2] == '01':
79
label = "(Quality Certified)"
83
def _handle_list_items(self, list_item_as_string):
84
list_item_as_string = list_item_as_string.strip()
85
if list_item_as_string:
86
return [item.replace('*', '\*') for item in list_item_as_string.split('\n')]
90
def _handle_text(self, txt):
91
lst = [' %s' % line for line in txt.split('\n')]
94
def _get_download_links(self):
95
def _is_connection_status_good(link):
96
server = "openerp.com"
99
conn = httplib.HTTPConnection(server)
100
conn.request("HEAD", link)
101
res = conn.getresponse()
102
if res.status in (200, ):
104
except (Exception, ), e:
105
logger = netsvc.Logger()
106
msg = "error connecting to server '%s' with link '%s'. Error message: %s" % (server, link, str(e))
107
logger.notifyChannel("base_module_doc_rst", netsvc.LOG_ERROR, msg)
111
versions = ('4.2', '5.0', 'trunk')
114
link = 'http://www.openerp.com/download/modules/%s/%s.zip' % (ver, self.dico['name'])
115
if _is_connection_status_good(link):
116
download_links.append(" * `%s <%s>`_" % (ver, link))
119
res = '\n'.join(download_links)
121
res = "(No download links available)"
124
def _write_header(self):
126
title = "%s (*%s*)" % (dico['shortdesc'], dico['name'])
127
title_underline = "=" * len(title)
128
dico['title'] = title
129
dico['title_underline'] = title_underline
130
dico['download_links'] = self._get_download_links()
134
".. module:: %(name)s",
135
" :synopsis: %(shortdesc)s %(quality_certified_label)s",
142
""" <link rel="stylesheet" href="../_static/hide_objects_in_sidebar.css" type="text/css" />""",
144
""".. tip:: This module is part of the Open ERP software, the leading Open Source """,
145
""" enterprise management system. If you want to discover Open ERP, check our """,
146
""" `screencasts <http://openerp.tv>`_ or download """,
147
""" `Open ERP <http://openerp.com>`_ directly.""",
151
""" <div class="js-kit-rating" title="" permalink="" standalone="yes" path="/%s"></div>""" % (dico['name'], ),
152
""" <script src="http://js-kit.com/ratings.js"></script>""",
155
"%(title_underline)s",
157
":Name: %(shortdesc)s",
158
":Version: %(latest_version)s",
159
":Author: %(author)s",
160
":Directory: %(name)s",
162
":Official module: %(official_module)s",
163
":Quality certified: %(quality_certified)s",
175
"You can download this module as a zip file in the following version:",
177
"%(download_links)s",
180
return '\n'.join(sl) % (dico)
182
def _write_reports(self):
186
reports = self.dico['report_list']
188
for report in reports:
191
sl.append(" * %s" % report)
193
sl.extend(["", "None", ""])
198
def _write_menus(self):
203
menus = self.dico['menu_list']
207
sl.append(" * %s" % menu)
209
sl.extend(["", "None", ""])
214
def _write_views(self):
219
views = self.dico['view_list']
223
sl.append(" * %s" % view)
225
sl.extend(["", "None", ""])
230
def _write_depends(self):
235
depends = self.dico['depends']
237
for dependency in depends:
238
sl.append(" * :mod:`%s`" % (dependency.name))
240
sl.extend(["", "None", ""])
244
def _write_objects(self):
245
def write_field(field_def):
246
if not isinstance(field_def, tuple):
247
logger = netsvc.Logger()
248
msg = "Error on Object %s: field_def: %s [type: %s]" % (obj_name.encode('utf8'), field_def.encode('utf8'), type(field_def))
249
logger.notifyChannel("base_module_doc_rst", netsvc.LOG_ERROR, msg)
252
field_name = field_def[0]
253
field_dict = field_def[1]
254
field_required = field_dict.get('required', '') and ', required'
255
field_readonly = field_dict.get('readonly', '') and ', readonly'
257
field_help_s = field_dict.get('help', '').strip()
259
field_help_s = "*%s*" % (field_help_s)
260
field_help = '\n'.join([' %s' % line.strip() for line in field_help_s.split('\n')])
265
":%s: %s, %s%s%s" % (field_name, field_dict.get('string', 'Unknown'), field_dict['type'], field_required, field_readonly),
276
for obj in self.objects:
277
obj_name = obj['object'].name
278
obj_model = obj['object'].model
279
title = "Object: %s (%s)" % (obj_name, obj_model)
286
#" single: %s object" % (obj_name),
290
for field in obj['fields']:
296
#" single: %s field" % (field[0]),
304
sl.extend(["", "None", ""])
306
return u'\n'.join([a.decode('utf8') for a in sl])
308
def _write_relationship_graph(self, module_name=False):
310
"Relationship Graph",
311
"------------------",
313
".. figure:: %s_module.png" % (module_name, ),
320
def write(self, module_name=False):
322
s += self._write_header()
323
s += self._write_depends()
324
s += self._write_reports()
325
s += self._write_menus()
326
s += self._write_views()
327
s += self._write_objects()
329
s += self._write_relationship_graph(module_name)
333
class wizard_tech_guide_rst(wizard.interface):
336
def _generate(self, cr, uid, data, context):
337
pool = pooler.get_pool(cr.dbname)
338
module_model = pool.get('ir.module.module')
339
module_ids = data['ids']
343
# create a temporary gzipped tarfile:
344
tgz_tmp_filename = tempfile.mktemp('_rst_module_doc.tgz')
346
tarf = tarfile.open(tgz_tmp_filename, 'w:gz')
348
modules = module_model.browse(cr, uid, module_ids)
349
for module in modules:
352
'shortdesc': module.shortdesc,
354
module_index.append(index_dict)
356
objects = self._get_objects(cr, uid, module)
357
module.test_views = self._get_views(cr, uid, module.id, context=context)
358
rstdoc = RstDoc(module, objects)
360
# Append Relationship Graph on rst
363
if module.file_graph:
364
graph_mod = base64.decodestring(module.file_graph)
366
module_data = module_model.get_relation_graph(cr, uid, module.name, context=context)
367
if module_data['module_file']:
368
graph_mod = base64.decodestring(module_data['module_file'])
370
module_name = module.name
372
tmpdir = tempfile.mkdtemp()
373
tmp_file_graph = tempfile.NamedTemporaryFile()
374
tmp_file_graph.write(graph_mod)
375
tmp_file_graph.file.flush()
376
tarf.add(tmp_file_graph.name, arcname= module.name + '_module.png')
378
tmp_file_graph.close()
380
out = rstdoc.write(module_name)
382
tmp_file = tempfile.NamedTemporaryFile()
383
tmp_file.write(out.encode('utf8'))
384
tmp_file.file.flush() # write content to file
385
tarf.add(tmp_file.name, arcname=module.name + '.rst')
390
tmp_file = tempfile.NamedTemporaryFile()
391
out = self._create_index(module_index)
392
tmp_file.write(out.encode('utf8'))
393
tmp_file.file.flush()
394
tarf.add(tmp_file.name, arcname='index.rst')
398
f = open(tgz_tmp_filename, 'rb')
402
if os.path.exists(tgz_tmp_filename):
404
os.unlink(tgz_tmp_filename)
406
logger = netsvc.Logger()
407
msg = "Temporary file %s could not be deleted. (%s)" % (tgz_tmp_filename, e)
408
logger.notifyChannel("warning", netsvc.LOG_WARNING, msg)
411
'rst_file': base64.encodestring(out),
412
'name': 'modules_technical_guide_rst.tgz'
415
def _get_views(self, cr, uid, module_id, context={}):
416
pool = pooler.get_pool(cr.dbname)
417
module_module_obj = pool.get('ir.module.module')
419
model_data_obj = pool.get('ir.model.data')
420
view_obj = pool.get('ir.ui.view')
421
report_obj = pool.get('ir.actions.report.xml')
422
menu_obj = pool.get('ir.ui.menu')
423
mlist = module_module_obj.browse(cr, uid, [module_id], context=context)
426
mnames[m.name] = m.id
428
'menus_by_module': [],
429
'reports_by_module': [],
430
'views_by_module': []
432
view_id = model_data_obj.search(cr, uid, [('module', 'in', mnames.keys()),
433
('model', 'in', ('ir.ui.view', 'ir.actions.report.xml', 'ir.ui.menu'))])
434
for data_id in model_data_obj.browse(cr, uid, view_id, context):
435
# We use try except, because views or menus may not exist
437
key = data_id['model']
438
if key == 'ir.ui.view':
439
v = view_obj.browse(cr, uid, data_id.res_id)
442
'inherit': v.inherit_id,
444
res[mnames[data_id.module]]['views_by_module'].append(v_dict)
445
elif key == 'ir.actions.report.xml':
446
res[mnames[data_id.module]]['reports_by_module'].append(report_obj.browse(cr, uid, data_id.res_id).name)
447
elif key == 'ir.ui.menu':
448
res[mnames[data_id.module]]['menus_by_module'].append(menu_obj.browse(cr, uid, data_id.res_id).complete_name)
453
def _create_index(self, module_index):
455
".. _module-technical-guide-link:",
457
"Module Technical Guide: Introspection report on objects",
458
"=======================================================",
464
for mod in module_index:
465
sl.append(" %s" % mod['name'])
469
def _get_objects(self, cr, uid, module):
471
objects = self._object_find(cr, uid, module)
473
fields = self._fields_find(cr, uid, obj.model)
481
def _object_find(self, cr, uid, module):
482
pool = pooler.get_pool(cr.dbname)
483
ids2 = pool.get('ir.model.data').search(cr, uid, [('module', '=', module.name), ('model', '=', 'ir.model')])
485
for mod in pool.get('ir.model.data').browse(cr, uid, ids2):
486
ids.append(mod.res_id)
487
modobj = pool.get('ir.model')
488
return modobj.browse(cr, uid, ids)
490
def _fields_find(self, cr, uid, obj):
491
pool = pooler.get_pool(cr.dbname)
492
modobj = pool.get(obj)
494
res = modobj.fields_get(cr, uid).items()
497
logger = netsvc.Logger()
498
msg = "Object %s not found" % (obj)
499
logger.notifyChannel("base_module_doc_rst", netsvc.LOG_ERROR, msg)
502
## def _object_doc(self, cr, uid, obj):
503
## pool = pooler.get_pool(cr.dbname)
504
## modobj = pool.get(obj)
505
## return modobj.__doc__
509
'actions': [_generate],
512
'arch': choose_file_form,
513
'fields': choose_file_fields,
515
('end', 'Close', 'gtk-close'),
521
wizard_tech_guide_rst('tech.guide.rst')