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 OpenERP software, the leading Open Source """,
145 """ enterprise management system. If you want to discover OpenERP, check our """,
146 """ `screencasts <http://openerp.tv>`_ or download """,
147 """ `OpenERP <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)
287 for field in obj['fields']:
296 sl.extend(["", "None", ""])
298 return u'\n'.join([a.decode('utf8') for a in sl])
300 def _write_relationship_graph(self, module_name=False):
302 "Relationship Graph",
303 "------------------",
305 ".. figure:: %s_module.png" % (module_name, ),
312 def write(self, module_name=False):
314 s += self._write_header()
315 s += self._write_depends()
316 s += self._write_reports()
317 s += self._write_menus()
318 s += self._write_views()
319 s += self._write_objects()
321 s += self._write_relationship_graph(module_name)
325 class wizard_tech_guide_rst(wizard.interface):
328 def _generate(self, cr, uid, data, context):
329 pool = pooler.get_pool(cr.dbname)
330 module_model = pool.get('ir.module.module')
331 module_ids = data['ids']
335 # create a temporary gzipped tarfile:
336 tgz_tmp_filename = tempfile.mktemp('_rst_module_doc.tgz')
338 tarf = tarfile.open(tgz_tmp_filename, 'w:gz')
340 modules = module_model.browse(cr, uid, module_ids)
341 for module in modules:
344 'shortdesc': module.shortdesc,
346 module_index.append(index_dict)
348 objects = self._get_objects(cr, uid, module)
349 module.test_views = self._get_views(cr, uid, module.id, context=context)
350 rstdoc = RstDoc(module, objects)
352 # Append Relationship Graph on rst
355 if module.file_graph:
356 graph_mod = base64.decodestring(module.file_graph)
358 module_data = module_model.get_relation_graph(cr, uid, module.name, context=context)
359 if module_data['module_file']:
360 graph_mod = base64.decodestring(module_data['module_file'])
362 module_name = module.name
364 tmpdir = tempfile.mkdtemp()
365 tmp_file_graph = tempfile.NamedTemporaryFile()
366 tmp_file_graph.write(graph_mod)
367 tmp_file_graph.file.flush()
368 tarf.add(tmp_file_graph.name, arcname= module.name + '_module.png')
370 tmp_file_graph.close()
372 out = rstdoc.write(module_name)
374 tmp_file = tempfile.NamedTemporaryFile()
375 tmp_file.write(out.encode('utf8'))
376 tmp_file.file.flush() # write content to file
377 tarf.add(tmp_file.name, arcname=module.name + '.rst')
382 tmp_file = tempfile.NamedTemporaryFile()
383 out = self._create_index(module_index)
384 tmp_file.write(out.encode('utf8'))
385 tmp_file.file.flush()
386 tarf.add(tmp_file.name, arcname='index.rst')
390 f = open(tgz_tmp_filename, 'rb')
394 if os.path.exists(tgz_tmp_filename):
396 os.unlink(tgz_tmp_filename)
398 logger = netsvc.Logger()
399 msg = "Temporary file %s could not be deleted. (%s)" % (tgz_tmp_filename, e)
400 logger.notifyChannel("warning", netsvc.LOG_WARNING, msg)
403 'rst_file': base64.encodestring(out),
404 'name': 'modules_technical_guide_rst.tgz'
407 def _get_views(self, cr, uid, module_id, context=None):
408 pool = pooler.get_pool(cr.dbname)
409 module_module_obj = pool.get('ir.module.module')
411 model_data_obj = pool.get('ir.model.data')
412 view_obj = pool.get('ir.ui.view')
413 report_obj = pool.get('ir.actions.report.xml')
414 menu_obj = pool.get('ir.ui.menu')
415 mlist = module_module_obj.browse(cr, uid, [module_id], context=context)
418 mnames[m.name] = m.id
420 'menus_by_module': [],
421 'reports_by_module': [],
422 'views_by_module': []
424 view_id = model_data_obj.search(cr, uid, [('module', 'in', mnames.keys()),
425 ('model', 'in', ('ir.ui.view', 'ir.actions.report.xml', 'ir.ui.menu'))])
426 for data_id in model_data_obj.browse(cr, uid, view_id, context):
427 # We use try except, because views or menus may not exist
429 key = data_id['model']
430 if key == 'ir.ui.view':
431 v = view_obj.browse(cr, uid, data_id.res_id)
434 'inherit': v.inherit_id,
436 res[mnames[data_id.module]]['views_by_module'].append(v_dict)
437 elif key == 'ir.actions.report.xml':
438 res[mnames[data_id.module]]['reports_by_module'].append(report_obj.browse(cr, uid, data_id.res_id).name)
439 elif key == 'ir.ui.menu':
440 res[mnames[data_id.module]]['menus_by_module'].append(menu_obj.browse(cr, uid, data_id.res_id).complete_name)
445 def _create_index(self, module_index):
447 ".. _module-technical-guide-link:",
449 "Module Technical Guide: Introspection report on objects",
450 "=======================================================",
456 for mod in module_index:
457 sl.append(" %s" % mod['name'])
461 def _get_objects(self, cr, uid, module):
463 objects = self._object_find(cr, uid, module)
465 fields = self._fields_find(cr, uid, obj.model)
473 def _object_find(self, cr, uid, module):
474 pool = pooler.get_pool(cr.dbname)
475 ids2 = pool.get('ir.model.data').search(cr, uid, [('module', '=', module.name), ('model', '=', 'ir.model')])
477 for mod in pool.get('ir.model.data').browse(cr, uid, ids2):
478 ids.append(mod.res_id)
479 modobj = pool.get('ir.model')
480 return modobj.browse(cr, uid, ids)
482 def _fields_find(self, cr, uid, obj):
483 pool = pooler.get_pool(cr.dbname)
484 modobj = pool.get(obj)
486 res = modobj.fields_get(cr, uid).items()
489 logger = netsvc.Logger()
490 msg = "Object %s not found" % (obj)
491 logger.notifyChannel("base_module_doc_rst", netsvc.LOG_ERROR, msg)
496 'actions': [_generate],
499 'arch': choose_file_form,
500 'fields': choose_file_fields,
502 ('end', 'Close', 'gtk-close'),
508 wizard_tech_guide_rst('tech.guide.rst')
511 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: