# -*- encoding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). # # 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 . # ############################################################################## from osv import osv, fields 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, 'quality_certified': bool(module.certificate) and 'yes' or 'no', 'official_module': str(module.certificate)[:2] == '00' and 'yes' or 'no', 'author': module.author, 'quality_certified_label': self._quality_certified_label(module), } self.objects = objects self.module = module def _quality_certified_label(self, module): label = "" certificate = module.certificate if certificate and len(certificate) > 1: if certificate[:2] == '00': # addons label = "(Official, Quality Certified)" elif certificate[:2] == '01': # extra addons label = "(Quality Certified)" return label 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('*', '\*') 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 = ('4.2', '5.0', 'trunk') download_links = [] for ver in versions: link = 'http://www.openerp.com/download/modules/%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 %(quality_certified_label)s", " :noindex:", ".. ", "", ".. raw:: html", "", "
", """ """, "", """.. tip:: This module is part of the OpenERP software, the leading Open Source """, """ enterprise management system. If you want to discover OpenERP, check our """, """ `screencasts `_ or download """, """ `OpenERP `_ directly.""", "", ".. raw:: html", "", """
""" % (dico['name'], ), """ """, "", "%(title)s", "%(title_underline)s", ":Module: %(name)s", ":Name: %(shortdesc)s", ":Version: %(latest_version)s", ":Author: %(author)s", ":Directory: %(name)s", ":Web: %(website)s", ":Official module: %(official_module)s", ":Quality certified: %(quality_certified)s", "", "Description", "-----------", "", "::", "", "%(description)s", "", "Download links", "--------------", "", "You can download this module as a zip file in the 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 wizard_tech_guide_rst(osv.osv_memory): _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: tmpdir = tempfile.mkdtemp() 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, } wizard_tech_guide_rst() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: