Added some icons for the kanban modules
[odoo/odoo.git] / addons / base_module_doc_rst / wizard / wizard_tech_guide_rst.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21 from os.path import join
22 import base64
23 import tempfile
24 import tarfile
25 import httplib
26
27 import netsvc
28 import wizard
29 import pooler
30 import os
31 import tools
32
33 import base_module_doc_rst
34
35
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"/>
42 </form>
43 '''
44
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},
48 }
49
50 class RstDoc(object):
51     def __init__(self, module, objects):
52         self.dico = {
53             'name': module.name,
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),
66         }
67         self.objects = objects
68         self.module = module
69
70     def _quality_certified_label(self, module):
71         label = ""
72         certificate = module.certificate
73         if certificate and len(certificate) > 1:
74             if certificate[:2] == '00':
75                 # addons
76                 label = "(Official, Quality Certified)"
77             elif certificate[:2] == '01':
78                 # extra addons
79                 label = "(Quality Certified)"
80
81         return label
82
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')]
87         else:
88             return []
89
90     def _handle_text(self, txt):
91         lst = ['  %s' % line for line in txt.split('\n')]
92         return '\n'.join(lst)
93
94     def _get_download_links(self):
95         def _is_connection_status_good(link):
96             server = "openerp.com"
97             status_good = False
98             try:
99                 conn = httplib.HTTPConnection(server)
100                 conn.request("HEAD", link)
101                 res = conn.getresponse()
102                 if res.status in (200, ):
103                     status_good = True
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)
108                 status_good = False
109             return status_good
110
111         versions = ('4.2', '5.0', 'trunk')
112         download_links = []
113         for ver in versions:
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))
117
118         if download_links:
119             res = '\n'.join(download_links)
120         else:
121             res = "(No download links available)"
122         return res
123
124     def _write_header(self):
125         dico = self.dico
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()
131
132         sl = [
133             "",
134             ".. module:: %(name)s",
135             "    :synopsis: %(shortdesc)s %(quality_certified_label)s",
136             "    :noindex:",
137             ".. ",
138             "",
139             ".. raw:: html",
140             "",
141             "      <br />",
142             """    <link rel="stylesheet" href="../_static/hide_objects_in_sidebar.css" type="text/css" />""",
143             "",
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.""",
148             "",
149             ".. raw:: html",
150             "",
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>""",
153             "",
154             "%(title)s",
155             "%(title_underline)s",
156             ":Module: %(name)s",
157             ":Name: %(shortdesc)s",
158             ":Version: %(latest_version)s",
159             ":Author: %(author)s",
160             ":Directory: %(name)s",
161             ":Web: %(website)s",
162             ":Official module: %(official_module)s",
163             ":Quality certified: %(quality_certified)s",
164             "",
165             "Description",
166             "-----------",
167             "",
168             "::",
169             "",
170             "%(description)s",
171             "",
172             "Download links",
173             "--------------",
174             "",
175             "You can download this module as a zip file in the following version:",
176             "",
177             "%(download_links)s",
178             "",
179             ""]
180         return '\n'.join(sl) % (dico)
181
182     def _write_reports(self):
183         sl = ["",
184               "Reports",
185               "-------"]
186         reports = self.dico['report_list']
187         if reports:
188             for report in reports:
189                 if report:
190                     sl.append("")
191                     sl.append(" * %s" % report)
192         else:
193             sl.extend(["", "None", ""])
194
195         sl.append("")
196         return '\n'.join(sl)
197
198     def _write_menus(self):
199         sl = ["",
200               "Menus",
201               "-------",
202               ""]
203         menus = self.dico['menu_list']
204         if menus:
205             for menu in menus:
206                 if menu:
207                     sl.append(" * %s" % menu)
208         else:
209             sl.extend(["", "None", ""])
210
211         sl.append("")
212         return '\n'.join(sl)
213
214     def _write_views(self):
215         sl = ["",
216               "Views",
217               "-----",
218               ""]
219         views = self.dico['view_list']
220         if views:
221             for view in views:
222                 if view:
223                     sl.append(" * %s" % view)
224         else:
225             sl.extend(["", "None", ""])
226
227         sl.append("")
228         return '\n'.join(sl)
229
230     def _write_depends(self):
231         sl = ["",
232               "Dependencies",
233               "------------",
234               ""]
235         depends = self.dico['depends']
236         if depends:
237             for dependency in depends:
238                 sl.append(" * :mod:`%s`" % (dependency.name))
239         else:
240             sl.extend(["", "None", ""])
241         sl.append("")
242         return '\n'.join(sl)
243
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)
250                 return ""
251
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'
256
257             field_help_s = field_dict.get('help', '').strip()
258             if field_help_s:
259                 field_help_s = "*%s*" % (field_help_s)
260                 field_help = '\n'.join(['    %s' % line.strip() for line in field_help_s.split('\n')])
261             else:
262                 field_help = ''
263
264             sl = ["",
265                   ":%s: %s, %s%s%s" % (field_name, field_dict.get('string', 'Unknown'), field_dict['type'], field_required, field_readonly),
266                   "",
267                   field_help,
268                  ]
269             return '\n'.join(sl)
270
271         sl = ["",
272               "",
273               "Objects",
274               "-------"]
275         if self.objects:
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)
280                 slo = [
281                        "",
282                        title,
283                        '#' * len(title),
284                        "",
285                       ]
286
287                 for field in obj['fields']:
288                     slf = [
289                            "",
290                            write_field(field),
291                            "",
292                           ]
293                     slo.extend(slf)
294                 sl.extend(slo)
295         else:
296             sl.extend(["", "None", ""])
297
298         return u'\n'.join([a.decode('utf8') for a in sl])
299
300     def _write_relationship_graph(self, module_name=False):
301         sl = ["",
302               "Relationship Graph",
303               "------------------",
304               "",
305               ".. figure:: %s_module.png" % (module_name, ),
306               "  :scale: 50",
307               "  :align: center",
308               ""]
309         sl.append("")
310         return '\n'.join(sl)
311
312     def write(self, module_name=False):
313         s = ''
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()
320         if module_name:
321             s += self._write_relationship_graph(module_name)
322         return s
323
324
325 class wizard_tech_guide_rst(wizard.interface):
326
327
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']
332
333         module_index = []
334
335         # create a temporary gzipped tarfile:
336         tgz_tmp_filename = tempfile.mktemp('_rst_module_doc.tgz')
337         try:
338             tarf = tarfile.open(tgz_tmp_filename, 'w:gz')
339
340             modules = module_model.browse(cr, uid, module_ids)
341             for module in modules:
342                 index_dict = {
343                     'name': module.name,
344                     'shortdesc': module.shortdesc,
345                 }
346                 module_index.append(index_dict)
347
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)
351
352                 # Append Relationship Graph on rst
353                 graph_mod = False
354                 module_name = False
355                 if module.file_graph:
356                     graph_mod = base64.decodestring(module.file_graph)
357                 else:
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'])
361                 if graph_mod:
362                     module_name = module.name
363                     try:
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')
369                     finally:
370                         tmp_file_graph.close()
371
372                 out = rstdoc.write(module_name)
373                 try:
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')
378                 finally:
379                     tmp_file.close()
380
381             # write index file:
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')
387         finally:
388             tarf.close()
389
390         f = open(tgz_tmp_filename, 'rb')
391         out = f.read()
392         f.close()
393
394         if os.path.exists(tgz_tmp_filename):
395             try:
396                 os.unlink(tgz_tmp_filename)
397             except Exception, e:
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)
401
402         return {
403             'rst_file': base64.encodestring(out),
404             'name': 'modules_technical_guide_rst.tgz'
405         }
406
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')
410         res = {}
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)
416         mnames = {}
417         for m in mlist:
418             mnames[m.name] = m.id
419             res[m.id] = {
420                 'menus_by_module': [],
421                 'reports_by_module': [],
422                 'views_by_module': []
423             }
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
428             try:
429                 key = data_id['model']
430                 if key == 'ir.ui.view':
431                     v = view_obj.browse(cr, uid, data_id.res_id)
432                     v_dict = {
433                         'name': v.name,
434                         'inherit': v.inherit_id,
435                         'type': v.type}
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)
441             except (KeyError, ):
442                 pass
443         return res
444
445     def _create_index(self, module_index):
446         sl = ["",
447               ".. _module-technical-guide-link:",
448               "",
449               "Module Technical Guide: Introspection report on objects",
450               "=======================================================",
451               "",
452               ".. toctree::",
453               "    :maxdepth: 1",
454               "",
455               ]
456         for mod in module_index:
457             sl.append("    %s" % mod['name'])
458         sl.append("")
459         return '\n'.join(sl)
460
461     def _get_objects(self, cr, uid, module):
462         res = []
463         objects = self._object_find(cr, uid, module)
464         for obj in objects:
465             fields = self._fields_find(cr, uid, obj.model)
466             dico = {
467                 'object': obj,
468                 'fields': fields
469             }
470             res.append(dico)
471         return res
472
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')])
476         ids = []
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)
481
482     def _fields_find(self, cr, uid, obj):
483         pool = pooler.get_pool(cr.dbname)
484         modobj = pool.get(obj)
485         if modobj:
486             res = modobj.fields_get(cr, uid).items()
487             return res
488         else:
489             logger = netsvc.Logger()
490             msg = "Object %s not found" % (obj)
491             logger.notifyChannel("base_module_doc_rst", netsvc.LOG_ERROR, msg)
492             return ""
493
494     states = {
495         'init': {
496             'actions': [_generate],
497             'result': {
498                 'type': 'form',
499                 'arch': choose_file_form,
500                 'fields': choose_file_fields,
501                 'state': [
502                     ('end', 'Close', 'gtk-close'),
503                 ]
504             }
505         },
506     }
507
508 wizard_tech_guide_rst('tech.guide.rst')
509
510
511 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: