Browse Source

Merge pull request #46 from acsone/8.0help_online_export

[8.0] Help online: new features
pull/52/head
Yannick Vaucher 10 years ago
parent
commit
a190a5af28
  1. 1
      .travis.yml
  2. 9
      help_online/__openerp__.py
  3. 9
      help_online/data/help_auto_backup.xml
  4. 25
      help_online/data/ir_config_parameter_data.xml
  5. 222
      help_online/i18n/fr.po
  6. 3
      help_online/models/__init__.py
  7. 275
      help_online/models/export_help_wizard.py
  8. 11
      help_online/models/help_online.py
  9. 49
      help_online/models/import_help_wizard.py
  10. 73
      help_online/models/ir_model.py
  11. 32
      help_online/security/help_online_rules.xml
  12. BIN
      help_online/static/description/icon.png
  13. 1
      help_online/static/description/index.html
  14. BIN
      help_online/static/src/img/snippet/snippet_thumbs.png
  15. 27
      help_online/tests/__init__.py
  16. 78
      help_online/tests/data/help_test_data.xml
  17. 161
      help_online/tests/test_export_help_wizard.py
  18. 52
      help_online/views/export_help_wizard_view.xml
  19. 15
      help_online/views/help_online_view.xml
  20. 46
      help_online/views/import_help_wizard_view.xml
  21. 28
      help_online/views/ir_ui_view_view.xml

1
.travis.yml

@ -12,6 +12,7 @@ virtualenv:
system_site_packages: true system_site_packages: true
install: install:
- pip install anybox.testing.openerp
- git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools - git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- travis_install_nightly - travis_install_nightly

9
help_online/__openerp__.py

@ -43,11 +43,20 @@ the group 'Help writer', the module generate a button allowing the creation an
help page. help page.
The help pages are created and managed via the website Module. The help pages are created and managed via the website Module.
Note: When updating the page prefix parameters, the record rules must be
adapted.
""", """,
'data': [ 'data': [
'data/help_auto_backup.xml', # must always be the first
'security/help_online_groups.xml', 'security/help_online_groups.xml',
'security/help_online_rules.xml',
'views/export_help_wizard_view.xml',
'views/import_help_wizard_view.xml',
'views/ir_ui_view_view.xml',
'views/help_online_view.xml', 'views/help_online_view.xml',
'views/website_help_online.xml', 'views/website_help_online.xml',
'data/ir_config_parameter_data.xml',
], ],
'qweb': [ 'qweb': [
'static/src/xml/help_online.xml', 'static/src/xml/help_online.xml',

9
help_online/data/help_auto_backup.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="wz_auto_backup" model="export.help.wizard">
</record>
<function model="export.help.wizard" name="auto_backup"/>
</data>
</openerp>

25
help_online/data/ir_config_parameter_data.xml

@ -0,0 +1,25 @@
<?xml version='1.0' encoding='UTF-8' ?>
<openerp>
<data noupdate="1">
<record id="help_online_autobackup_path" model="ir.config_parameter">
<field name="key">help_online_autobackup_path</field>
<field name="value">False</field>
</record>
</data>
<data noupdate="1">
<record id="help_online_page_prefix" model="ir.config_parameter">
<field name="key">help_online_page_prefix</field>
<field name="value">help</field>
</record>
</data>
<data noupdate="1">
<record id="help_online_template_prefix" model="ir.config_parameter">
<field name="key">help_online_template_prefix</field>
<field name="value">help-template</field>
</record>
</data>
</openerp>

222
help_online/i18n/fr.po

@ -0,0 +1,222 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * help_online
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-10-14 09:06+0000\n"
"PO-Revision-Date: 2014-10-14 09:06+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: help_online
#. openerp-web
#: code:addons/help_online/static/src/js/help_online.js:83
#, python-format
msgid "Cancel"
msgstr "Annuler"
#. module: help_online
#: view:export.help.wizard:help_online.export_help_wizard_view
#: view:import.help.wizard:help_online.import_help_wizard_view
msgid "Close"
msgstr "Fermer"
#. module: help_online
#. openerp-web
#: code:addons/help_online/static/src/js/help_online.js:81
#, python-format
msgid "Confirm"
msgstr "Confirmer"
#. module: help_online
#: code:addons/help_online/models/help_online.py:60
#, python-format
msgid "Create Help page for %s"
msgstr "Créer la page d'aide pour %s"
#. module: help_online
#: field:export.help.wizard,create_uid:0
#: field:help.online,create_uid:0
#: field:import.help.wizard,create_uid:0
msgid "Created by"
msgstr "Créé par"
#. module: help_online
#: field:export.help.wizard,create_date:0
#: field:help.online,create_date:0
#: field:import.help.wizard,create_date:0
msgid "Created on"
msgstr "Créé le"
#. module: help_online
#: view:export.help.wizard:help_online.export_help_wizard_view
msgid "Export"
msgstr "Export"
#. module: help_online
#: model:ir.actions.act_window,name:help_online.action_export_help_wizard
msgid "Export Help"
msgstr "Export des pages d'aide"
#. module: help_online
#: view:export.help.wizard:help_online.export_help_wizard_view
msgid "Export Help Data"
msgstr "Export des pages d'aide"
#. module: help_online
#: model:ir.model,name:help_online.model_export_help_wizard
#: model:ir.ui.menu,name:help_online.menu_help_export
msgid "Export Help Online"
msgstr "Export des pages d'aide"
#. module: help_online
#: field:export.help.wizard,export_filename:0
msgid "Export XML Filename"
msgstr "Fichier XML"
#. module: help_online
#: model:ir.ui.menu,name:help_online.menu_help
#: model:ir.ui.menu,name:help_online.menu_help_main
msgid "Help Online"
msgstr "Aide en ligne"
#. module: help_online
#: code:addons/help_online/models/help_online.py:55
#, python-format
msgid "Help on %s"
msgstr "Aide sur %s"
#. module: help_online
#: model:res.groups,name:help_online.help_online_group_reader
msgid "Help reader"
msgstr "Aide: lecteur"
#. module: help_online
#: model:res.groups,name:help_online.help_online_group_writer
msgid "Help writer"
msgstr "Aide: rédacteur"
#. module: help_online
#: field:export.help.wizard,id:0
#: field:help.online,id:0
#: field:import.help.wizard,id:0
msgid "ID"
msgstr "ID"
#. module: help_online
#: view:import.help.wizard:help_online.import_help_wizard_view
msgid "Import"
msgstr "Import"
#. module: help_online
#: model:ir.actions.act_window,name:help_online.action_import_help_wizard
msgid "Import Help"
msgstr "Import des pages d'aide"
#. module: help_online
#: view:import.help.wizard:help_online.import_help_wizard_view
msgid "Import Help Data"
msgstr "Import des pages d'aide"
#. module: help_online
#: model:ir.ui.menu,name:help_online.menu_help_import
msgid "Import Help Online"
msgstr "Import des pages d'aide"
#. module: help_online
#: field:export.help.wizard,write_uid:0
#: field:help.online,write_uid:0
#: field:import.help.wizard,write_uid:0
msgid "Last Updated by"
msgstr "Mis à jour par"
#. module: help_online
#: field:export.help.wizard,write_date:0
#: field:help.online,write_date:0
#: field:import.help.wizard,write_date:0
msgid "Last Updated on"
msgstr "Mis à jour le"
#. module: help_online
#: code:addons/help_online/models/export_help_wizard.py:238
#, python-format
msgid "No data to export !"
msgstr "Aucune page à exporter !"
#. module: help_online
#: code:addons/help_online/models/help_online.py:33
#, python-format
msgid "No page prefix parameter specified !"
msgstr "Le paramètre spécifiant le préfixe des pages n'est pas configuré !"
#. module: help_online
#. openerp-web
#: code:addons/help_online/static/src/js/help_online.js:88
#, python-format
msgid "Ok"
msgstr "Ok"
#. module: help_online
#. openerp-web
#: code:addons/help_online/static/src/js/help_online.js:107
#, python-format
msgid "Page does not exist. Do you want to create?"
msgstr "La page n'existe pas. Voulez-vous la créer?"
#. module: help_online
#: field:import.help.wizard,source_file:0
msgid "Source File"
msgstr "Fichier de données"
#. module: help_online
#: view:export.help.wizard:help_online.export_help_wizard_view
msgid "This wizard allow you to export all QWeb views\n"
" related to help online. The result will be an Odoo\n"
" data xml file."
msgstr "Cet assistant vous permet d'exporter toutes les vues QWeb \n"
" concernant l'aide. Le résultat sera un\n"
" fichier de données Odoo."
#. module: help_online
#: view:import.help.wizard:help_online.import_help_wizard_view
msgid "This wizard allow you to import QWeb views\n"
" related to help online. The required file format is an Odoo\n"
" data xml file."
msgstr "Cet assistant vous permet d'importer toutes les vues QWeb\n"
" concernant l'aide. Le format de fichier requis est un\n"
" fichier de données Odoo."
#. module: help_online
#: code:addons/help_online/models/export_help_wizard.py:275
#, python-format
msgid "Unable to write autobackup file in given directory: %s"
msgstr "Impossible d'écrire le fichier de sauvegarde dans le répertoire spécifié: %s"
#. module: help_online
#: view:ir.ui.view:help_online.view_view_search
msgid "Website Page"
msgstr "Page du site"
#. module: help_online
#: view:ir.ui.view:help_online.view_view_form
msgid "Website Page?"
msgstr "Page du site?"
#. module: help_online
#: model:ir.actions.act_window,name:help_online.action_website_pages
#: model:ir.ui.menu,name:help_online.menu_help_pages
msgid "Website Pages"
msgstr "Pages du site"
#. module: help_online
#: field:export.help.wizard,data:0
msgid "XML"
msgstr "XML"

3
help_online/models/__init__.py

@ -19,3 +19,6 @@
# #
############################################################################## ##############################################################################
from . import help_online from . import help_online
from . import export_help_wizard
from . import import_help_wizard
from . import ir_model

275
help_online/models/export_help_wizard.py

@ -0,0 +1,275 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Authors: Cédric Pigeon
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
#
# 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/>.
#
##############################################################################
import logging
import base64
import time
import copy
from lxml import etree as ET
from openerp import models, fields, api, exceptions
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
PAGE_PREFIX_PARAMETER = 'help_online_page_prefix'
TEMPLATE_PREFIX_PARAMETER = 'help_online_template_prefix'
AUTOBACKUP_PARAMETER = 'help_online_autobackup_path'
HELP_ONLINE_SNIPPET_IMAGE_PATH = '/help_online/static/src/'\
'img/snippet/snippet_thumbs.png'
class ExportHelpWizard(models.TransientModel):
_name = "export.help.wizard"
_description = 'Export Help Online'
data = fields.Binary('XML', readonly=True)
export_filename = fields.Char('Export XML Filename', size=128)
def _manage_images_on_page(self, page_node, data_node):
"""
- Extract images from page and generate a xml node
- Replace db id in url with xml id
"""
def substitute_id_by_xml_id(img_elem):
new_src = False
attach_id = False
img_src = img_elem.get('src')
if 'id=' in img_src:
id_pos = img_src.index('id=') + 3
attach_id = img_elem.get('src')[id_pos:]
new_src = img_src.replace(attach_id, xml_id)
else:
fragments = img_src.split('ir.attachment/')
attach_id, trail = fragments[1].split('_', 1)
new_src = "/website/image/ir.attachment/%s|%s" % \
(xml_id, trail)
return new_src, attach_id
i_img = 0
img_model = 'ir.attachment'
for img_elem in page_node.iter('img'):
if img_model in img_elem.get('src'):
i_img += 1
xml_id = "%s_img_%s" % \
(page_node.attrib['name'], str(i_img).rjust(2, '0'))
new_src, attach_id = substitute_id_by_xml_id(img_elem)
if not attach_id:
continue
image = self.env[img_model].browse(int(attach_id))
if not image:
continue
img_elem.attrib['src'] = new_src
img_node = ET.SubElement(data_node,
'record',
attrib={'id': xml_id,
'model': img_model})
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'datas'})
field_node.text = str(image.datas)
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'datas_fname'})
field_node.text = image.datas_fname
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'name'})
field_node.text = image.name
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'res_model'})
field_node.text = image.res_model
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'mimetype'})
field_node.text = image.mimetype
data_node.append(img_node)
def _clean_href_urls(self, page_node, page_prefix, template_prefix):
"""
Remove host address for href urls
"""
for a_elem in page_node.iter('a'):
if not a_elem.get('href'):
continue
href = a_elem.get('href')
if not href.startswith('http'):
continue
page_url = '/page/%s' % page_prefix
template_url = '/page/%s' % template_prefix
if page_url not in href and template_url not in href:
continue
elif page_url in href and template_url not in href:
pass
elif page_url not in href and template_url in href:
page_url = template_url
else:
if page_prefix in template_prefix:
page_url = template_url
else:
pass
if page_url:
trail = href.split(page_url, 1)[1]
a_elem.attrib['href'] = page_url + trail
def _generate_snippet_from_template(self, page_node,
template_id, template_prefix):
"""
Generate a website snippet from a template
"""
page = copy.deepcopy(page_node)
snippet = ET.Element('template')
snippet.attrib['id'] = template_id + '_snippet'
snippet.attrib['inherit_id'] = 'website.snippets'
snippet.attrib['name'] = page_node.attrib['name']
xpath = ET.SubElement(snippet,
'xpath',
attrib={'expr': "//div[@id='snippet_structure']",
'position': 'inside'})
main_div = ET.SubElement(xpath,
'div')
thumbnail = ET.SubElement(main_div,
'div',
attrib={'class': 'oe_snippet_thumbnail'})
ET.SubElement(thumbnail,
'img',
attrib={'class': 'oe_snippet_thumbnail_img',
'src': HELP_ONLINE_SNIPPET_IMAGE_PATH})
span = ET.SubElement(thumbnail,
'span',
attrib={'class': 'oe_snippet_thumbnail_title'})
span.text = page_node.attrib['name'].replace(template_prefix, '')
body = ET.SubElement(main_div,
'section',
attrib={'class': 'oe_snippet_body '
'mt_simple_snippet'})
template = page.find(".//div[@id='wrap']")
for node in template.getchildren():
body.append(node)
return snippet
def _get_qweb_views_data(self):
parameter_model = self.env['ir.config_parameter']
page_prefix = parameter_model.get_param(PAGE_PREFIX_PARAMETER,
False)
template_prefix = parameter_model.get_param(TEMPLATE_PREFIX_PARAMETER,
False)
if not page_prefix or not template_prefix:
return False
domain = [('type', '=', 'qweb'),
('page', '=', True),
'|',
('name', 'like', '%s%%' % page_prefix),
('name', 'like', '%s%%' % template_prefix)]
view_data_list = self.env['ir.ui.view'].search_read(domain,
['arch', 'name'],
order='name')
xml_to_export = ET.Element('openerp')
data_node = ET.SubElement(xml_to_export, 'data')
for view_data in view_data_list:
parser = ET.XMLParser(remove_blank_text=True)
root = ET.XML(view_data['arch'], parser=parser)
root.tag = 'template'
template_id = root.attrib.pop('t-name')
root.attrib['name'] = view_data['name'].replace('website.', '')
root.attrib['id'] = template_id
root.attrib['page'] = 'True'
self._manage_images_on_page(root, data_node)
self._clean_href_urls(root, page_prefix, template_prefix)
data_node.append(root)
if root.attrib['name'].startswith(template_prefix):
snippet = self._generate_snippet_from_template(root,
template_id,
template_prefix)
data_node.append(snippet)
if len(view_data_list) > 0:
return ET.tostring(xml_to_export, encoding='utf-8',
xml_declaration=True,
pretty_print=True)
else:
return False
@api.multi
def export_help(self):
"""
Export all Qweb views related to help online in a Odoo
data XML file
"""
xml_data = self._get_qweb_views_data()
if not xml_data:
raise exceptions.Warning(_('No data to export !'))
out = base64.encodestring(xml_data)
self.write({'data': out,
'export_filename': 'help_online_data.xml'})
return {
'name': _('Export Help'),
'type': 'ir.actions.act_window',
'res_model': self._name,
'view_mode': 'form',
'view_type': 'form',
'res_id': self.id,
'views': [(False, 'form')],
'target': 'new',
}
@api.model
def auto_backup(self):
"""
Export data to a file on home directory of user
"""
parameter_model = self.env['ir.config_parameter']
autobackup_path = parameter_model.get_param(AUTOBACKUP_PARAMETER,
False)
if autobackup_path:
xml_data = self._get_qweb_views_data()
try:
timestr = time.strftime("%Y%m%d-%H%M%S")
filename = '%s/help_online_backup-%s.xml' % (autobackup_path,
timestr)
backup_file = open(filename,
'w')
backup_file.write(xml_data)
backup_file.close
except:
_logger.warning(_('Unable to write autobackup file '
'in given directory: %s'
% autobackup_path))

11
help_online/models/help_online.py

@ -18,15 +18,20 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
from openerp.osv import orm
from openerp import models, exceptions
from openerp.tools.translate import _ from openerp.tools.translate import _
class HelpOnline(orm.TransientModel):
class HelpOnline(models.TransientModel):
_name = 'help.online' _name = 'help.online'
def _get_view_name(self, model, view_type, domain=None, context=None): def _get_view_name(self, model, view_type, domain=None, context=None):
name = 'help-%s' % model.replace('.', '-')
parameter_model = self.env['ir.config_parameter']
page_prefix = parameter_model.get_param('help_online_page_prefix',
False)
if not page_prefix:
raise exceptions.Warning(_('No page prefix parameter specified !'))
name = '%s-%s' % (page_prefix, model.replace('.', '-'))
return name return name
def page_exists(self, name): def page_exists(self, name):

49
help_online/models/import_help_wizard.py

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs.
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contact a Free Software
# Service Company.
#
# 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 import models, fields, api
from openerp.tools import convert
import base64
from cStringIO import StringIO
class ImportHelpWizard(models.TransientModel):
_name = "import.help.wizard"
source_file = fields.Binary('Source File')
@api.one
def import_help(self):
source_file = base64.decodestring(self.source_file)
convert.convert_xml_import(self.env.cr,
self._module,
StringIO(source_file),
idref=None,
mode='init',
noupdate=False,
report=None)

73
help_online/models/ir_model.py

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Authors: Cédric Pigeon
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
#
# 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 import models, api
from lxml import etree as ET
class ir_model_data(models.Model):
_inherit = 'ir.model.data'
@api.model
def _update(self, model, module, values, xml_id=False, store=True,
noupdate=False, mode='init', res_id=False):
if model == 'ir.ui.view':
parameter_model = self.env['ir.config_parameter']
page_prefix = parameter_model.get_param('help_online_page_prefix',
False)
if page_prefix and xml_id.startswith('website.%s' % page_prefix):
xml_str = self.manageImageReferences(values['arch'], module)
values['arch'] = xml_str
return super(ir_model_data, self)._update(model,
module,
values,
xml_id=xml_id,
store=store,
noupdate=noupdate,
mode=mode,
res_id=res_id)
def manageImageReferences(self, xml_str, module):
parser = ET.XMLParser(remove_blank_text=True)
root = ET.XML(xml_str, parser=parser)
img_model = 'ir.attachment'
for img_elem in root.iter('img'):
if img_model in img_elem.get('src'):
img_src = img_elem.get('src')
try:
if '/ir.attachment/' in img_src:
fragments = img_src.split('/ir.attachment/')
xml_id = fragments[1].split('|')[0]
img_src = img_src.replace("|", "_")
else:
id_pos = img_src.index('id=') + 3
xml_id = img_elem.get('src')[id_pos:]
img_id = self.get_object_reference(module,
xml_id)
img_elem.attrib['src'] = img_src.replace(xml_id,
str(img_id[1]))
except:
continue
return ET.tostring(root, encoding='utf-8', xml_declaration=False)

32
help_online/security/help_online_rules.xml

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="online_help_confidential_rule" model="ir.rule">
<field name="name">Online Help Hidden by Default</field>
<field name="model_id" ref="base.model_ir_ui_view"/>
<field name="domain_force">[
'|',
('type', '!=', 'qweb'),
('name','not like','help-%'),
]</field>
<field name="groups" eval="[(6, 0, [
ref('base.group_portal'),
ref('base.group_public'),
ref('base.group_user'),
])]"/>
<field name="perm_read" eval="1"/><field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/><field name="perm_unlink" eval="0"/>
</record>
<record id="online_help_reader_rule" model="ir.rule">
<field name="name">Online Help for Help Reader</field>
<field name="model_id" ref="base.model_ir_ui_view"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(6, 0, [
ref('help_online.help_online_group_reader'),
])]"/>
<field name="perm_read" eval="1"/><field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/><field name="perm_unlink" eval="0"/>
</record>
</data>
</openerp>

BIN
help_online/static/description/icon.png

After

Width: 225  |  Height: 218  |  Size: 79 KiB

1
help_online/static/description/index.html

@ -12,5 +12,6 @@
<img alt="help_online_create_page.png" src="help_online_create_page.png" width="80%" height="80%"/> <img alt="help_online_create_page.png" src="help_online_create_page.png" width="80%" height="80%"/>
<p>The help pages are created and managed via the website Module.</p> <p>The help pages are created and managed via the website Module.</p>
<br/> <br/>
<p>If you want to export your work, you simply have to use the export wizard through the settings menu.</p>
</div> </div>
</div> </div>

BIN
help_online/static/src/img/snippet/snippet_thumbs.png

After

Width: 169  |  Height: 180  |  Size: 42 KiB

27
help_online/tests/__init__.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
#
# 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 . import test_export_help_wizard
fast_suite = [
]
checks = [
test_export_help_wizard,
]

78
help_online/tests/data/help_test_data.xml

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="test_img_1" model="ir.attachment">
<field name="datas">iVBORw0KGgoAAAANSUhEUgAAANwAAAAzCAIAAABzKvGBAAAOOUlEQVR42u1beVRU5xWfYcZh3wao
cSnLAIMRIw1WZVdTPTGLadIsoqBCwiJatFUjmyZVqgxL03NsFgLYuCH2tCc9bZo0icbIqqKpwRiO
wDC4gMgyI1sEBhh633zymOW9N9tzZkze/eOeYb737r3f/X7f797vzYMtlUrZbPbk5CSjGW0lmi2T
yawhDkYzeloDU7IYYcSaBAOl5XcGoxmtqnt7ey29MRhhRE0YUDJidcLu6emxCsZmNKOnNMaU1hAH
oxk9rbu7u60iDkYzGmdKKN/WEAejGa3GlJbuaxlhRE0YUDJidcLu6uqyCsZmNKPxnhKY0hriME5D
Q9zR0dHf32/pvU0srq6u3t7efD5f51x6e3stPhH9o5VKpRBtX1+fEV7c3NzAi7u7O5WXu3fvWhxb
xunr16/DKgoEAicnJzs7O1psVlVVxcTE0BLhyMjI0NCQRCKZOXOmr68vxZVNTU0wEX9/f0dHR3t7
e6Mjv3TpElcpsPbwpRGImZiYGB8f37lzJ4Wv5uZmPFpIu0Fxjo6O4jnx8fEhZUoo39aAMCM4Eub2
5JNP8ng8Gi1XV1dHR0fTGKdcLr9y5YpQKCTjBmCd1tbW0NBQAJOJkTc0NMyYMQMSYjQox5QSHx9P
Fq1MJkPRcjgco6MFFygnwJqkTGnWIkGTwALMmTPH09OTXrMIlPTaRKU5JCTkoU4EIm9sbLS1tQX2
YhkLylGlhIWFkUV79epViNbDw8PEaFEDsHDhQsLRRxWUsAaQOyAG2s3SDkqo41BYyczSNRGwIxaL
7ZWi+r1OdAI54Z+HlRIQEEARbXh4OOoQTBGAfn19PZkXdmdnp8VrsRG6pqYmKiqKdssIlOaMli6P
hKBUKIKfT4yca0uxY25XH/53o7JzUAWl4dFeP5FcVKlmWrCxcG+M+wTaGAblBGNKiyPskUDPQ4qW
Lo9gp6WlBRDp4OCAc+Ro9Kvb5vOpSUv6XcGpKkdE1ffv3wdQBgYGGhJt8/GkwkpS8/4bC/cANA1b
wTt37lgcYUbo2trayMhI2i2jpJszWro8EoJyIPKVNxd4dNX/vuBvNtwpOsRHx17e8c6SmT1X3j5x
zg3uglEclPpG2wz8WKWzWPtvKsqKdlMoFHrmBCvfJmbExsaGLa08kHHrpZK4eUTXsHor80rZyVkx
nupMDuFy7lXl7r75cukGodKOjazywO7jrQ/of4MoexlffSYWRM9DwjqBR2lVXtYJCeUyC+JFmTF8
DTsaoAQQyCJeyV7o2VX/u0P/csS/x2VwdbJo0c+6v8k6epbv4uKiCkq9otWK8+ncf74RNAZ+ESqu
l619+/SDoRVZx+L85aq4pFpBYErdfSm5gBUer/XYJtFXrOU7S9YHEV3TVy36kJueE+06Pj5OcSNs
ZYcbJ2PrIk4lzYcL4GJohycmJgj9ouzoEWBvlSgby9yyN0viAnVerafZBwwhiD+YGaPPsZnCrPYQ
t6/64O7jYkqDwD2ZUa6TKocYQlBKw1/JCfHsvpzx0VcPYIdfD/fKIl7FRr/J/OiMu5ubmwZT6oq2
tyov+0Tb9BAgcpP/8GhDSWL+OYxR8nNWPsYdqM5NL0NTWZV5JE4gl+uTE5NACdPorxHtOtoauGrV
xOmxl4hAyWZLa0RlnG25Uc4jOCgJb4S2pq08sfznf94T44ayBoicJDk86oMeteKyIqsszh/WifoW
vcxOBY/+jNlVGi+cNNqs9hDkAbBFccKFWcCJHnasvqBUgZ2qEe1RA0DZWynKLqegc7/4vOxlnjNm
tJYniBBdBrxxKCvCEWcZKlB2dHQYXZWAogckEpvg4Fmyr7PSxYAtodY1HE7LyTzpM3uXucqn2Rsy
3icWc5944jHpWbjxxQ/XPW5jw+P1V+YWjyVnRRP1xRq6rq4uIiKC4prmEynk3Y5g/YGM5V4Ed6FM
Ednsqc7PIa2pSho2Llptj1AWRVknWilRDkSUGe2uYYdGUOqMVlYNMbaRBxiwqWhPlOsYrMOppMIz
6LtV+47E+cBe0pkTDJQsYwXhEhBmO1Cds60VsKXNlDxJReLl6I/igbnlFDcGQ+12uFG2ruKGv7gV
WxBBHJRGD1IGQlMiGWw6mfoO+XkQoejw+oBx/c1yOOKTSYVnKXECQF9GUsspotUe4vbV5GXoLt8Z
kS6qTAl26AUldbQt5amEe37Vvn9sDeFCYGAHrLGaVI7mwtR39kK9HEH1iiIn7Pb2dhO7eKzcDNUi
bGkwJSBPUpFyKaJ8nd8wqsWqo9jPD4M12I3FsU/Y2jr+UJvz28qYQ/tXekHKrpXG1YYqUU7o9/z5
8+Hh4USnrpaTKX86x9IhKzKPrBPIFVqnKJQpbY9crvjUG4WndVjFNlI0X6F/tIQeIZ8ALIrf8fDy
rRo/vaDUGW1rxWai50DCzSUFqzxgqSewCK+WqZ3NhWmH9sXYDw2hkxBFTjCmNPF0qYqtIM3aLa5I
+t/iIxsEcgIEqKJ5PocDCYWmE0XM5bYc3fj32flZkVgJ0GstcY/T9YIClFnHYn2HWVqIJ8uUrW1b
RWL+lzqsBm4oyIKapX+0hB4BjjZKIcu5Qimw8Bp26GVK6mjvVednEnQzT791KtF/dPTumX0Zx7VG
hWl/yV3uMDQ0NjZGnROMKXWtoA7h8XgPsFUcK9QYkpx6/Zvov8b5qdZuwhuDlAUdIVI5JCl//ePZ
BdmRLnJCpyg72t9DS+DsPHxub/oHzbpBqX2KIjPLw2ZSQAlKYcr7B8PtB4HA9I+WcAjrKXNOUvRr
IH7QK8R4aNihlympo+W2VhA/Mn8qu2Tlzbzscu34hWmluctdBgcH0XmXIicPEZQwycG6gv/O3Q+1
W/VhEOGNGChbypOLWDuK14MR3kDtwRJeCoZJw0AJTu2U0v/13pRiMmAKNxXlRDiPGALKgQt5u0ta
yOylvX/wVy4jI7CihM+wDALljP5anT2lYGOhRk9pblByW/WpSCoSlFZSEO30AwhKERUob9++bXpP
6fBDXc428YvF6wLV+rC+WlHZRGoOIEtB9Awc1f092yW//mDtfC4XMNpzdv/Uo5YVOeVJfsMYmgn9
XrhwISwsjDAeJYZ4SmSKS37z1udook//8ePU+WgVoXxAakBr34syRdQVcJycnGCmbKX0fpmJIx7a
qNwVrniTZ2i02h7Bi6OjI3gkW17wAtDR6CmpQKl8Tunq6qoBSornlDqjhfCG6vJ3HFF9SBC0uST/
KTf5ZNeXe9PLNPlg9cGPU+YNDQ1hpx9l10SREwyUBsCdSCCJAC/IESyzPt8TXqB8lo4JFHGUMrlS
yJ5ToilRRIUd7ZXvcTUffm3PF6zApHdzopzQHkUNGaFlMrMIlyg20PZt5bH7z2DnyWLRMheMIBHE
yYKhiFZ7COyjUw6ZNXTW0aBksEMGSurlA1AePeuh8YuOPtHa2g6ez9tdOl09hMnvHVjuJufIzr6V
flgdlEFppYWwc6F2440cRU7Yt27dMpEpWdgTEw7KkT7fq57N8eMkuh69FoB6ebhRQfIbI+iLFy+G
hoZSv+EL3gGagEv4ALmAjE8qhWIuKFM654texoHPsIRgGW0qsuvhmoaGhqVLlxrtUR9NCMq+voDX
tq8U2LNIZVj86YEjTXw+cLMqKPWJFpbP0fHW8fgDX1CjnhW0paxoJX8MaBK1N/gKknnBmNL0jJhf
X7t2bfbs2e7u7jqvRCdZdGLVaZkiU2pZw5oTLs5Y1Nffu3evu7t73rx5pnjUJ3LtFzIAZAAF6FUg
TkTzGoyLWgXoTGB7azClPtEqb79f+fY20u4dyvYnWx8fH0eWYQPrtRuBKVmPoMhksps3by5YsIDe
93xR0mk0CCQK+0cgEEBLR+YRKN/0WWiDEnkHHJC9QoBQBU0O3jWpMiWZl0WLFuE/gbKnnqra2Q2c
ztys/sQDg2OwQgGnAmi4wbJqFwchffvtt2ReHlVQgrS1tfX19fn4+CiTYkeLTRpBiVYCdo6Xl9fc
uXPJLvv+++9nzZrF5/MNsU0cuTYoWUo6pGh2UTeC968IlIsXLw4KIny1htXY2AjRQoFS/RIhGwTA
iloy1lTjC0BE/2Kh8fgFqkdPT49QKCSOCrJm8VpstB4YGGhvb4cKRQuMaBcoiwBHdIYgm4VUKgVe
QJRvSjbIQKktGdlFm1Ni/XwJ9gkC5Zo1a8hihgIF0QYHB2tEi/oZ1MQj0p1QCvo1RKPDgT9R9XB2
dibujgCUll47K5L6+volS5aY2emNGzeA8r29vU2hfIhcz38c+0Pue4/N9HxmdbSP92yNIcRqCQkJ
FI4AMHi04E51iD0lrCmG1ggDjAPuAdaenp5z5swhc8GGjFgD5/3EdX9/f0dHh3koP3XLnu8aKp95
dm3s2ucDA3zx74HXASjUvI40FCiIdnBw0AjvwI7ghYwjp5nSGlaF0WbTSyOev9vx3Z3OrrWxybt2
bv5FSLA1RKWmgSnNsDsZsR5BoIQPqri0dFBqwoDyJyc4KFnWikt2W1ub6VYYeYQkLHINDkrWFC53
7ki1HlwyoPzJiQYoFQpFR0fn+vg068ElWyKRWEVvy2hzaVVQ4u8Lt3d0bkpIhzoesnC+xSPEmNIa
MsVos+nwqBcQKHFEoqfcwJfJqbusApfAlJZma0bMKgiUGojEcfnbbTkWr+Ps+vp6S2eJEbPKcy8k
IFBqIBJJZ2dX+vY97x7KtWCEbOp3ARn949PefotuSi4jRLa33/Hy8gAsrovbogqLzz+roMsjetXc
YFBaQ6YYbTbt679Y0nIRIfKXS1d/8J7oqRWRGxO2b92S+NyzK2n3aAwoCf/7jpEfsQAoxU3nESI/
+0/5lq2Zn35yDKp2Usqbl+t1vUVuuIyMjBh6y/8Bzpl/yXfWC20AAAAASUVORK5CYII=
</field>
<field name="index_content">image</field>
<field name="datas_fname">test.png</field>
<field name="name">test.png</field>
<field name="res_model">ir.ui.view</field>
<field name="mimetype">image/png</field>
</record>
</data>
</openerp>

161
help_online/tests/test_export_help_wizard.py

@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Authors: Cédric Pigeon
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
#
# 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/>.
#
##############################################################################
import logging
import base64
from lxml import etree as ET
from anybox.testing.openerp import SharedSetupTransactionCase
_logger = logging.getLogger(__name__)
class test_export_help_wizard(object):
_data_files = ('data/help_test_data.xml',)
_module_ns = 'help_online'
def createPage(self, pageName, imgXmlId=False):
imgId = False
if imgXmlId:
imgId = self.ref('%s.%s' % (self._module_ns, imgXmlId))
rootNode = ET.Element('t')
rootNode.attrib['name'] = pageName
rootNode.attrib['t-name'] = "website.%s" % pageName
tNode = ET.SubElement(rootNode,
't',
attrib={'t-call': 'website.layout'})
structDivNode = ET.SubElement(tNode,
'div',
attrib={'class': 'oe_structure oe_empty',
'id': 'wrap'})
sectionNode = ET.SubElement(structDivNode,
'section',
attrib={'class': 'mt16 mb16'})
containerNode = ET.SubElement(sectionNode,
'div',
attrib={'class': 'container'})
rowNode = ET.SubElement(containerNode,
'div',
attrib={'class': 'row'})
bodyDivNode = ET.SubElement(rowNode,
'div',
attrib={'class': 'col-md-12 '
'text-center mt16 mb32'})
style = "font-family: 'Helvetica Neue', Helvetica,"\
" Arial, sans-serif; color: rgb(51, 51, 51);"\
" text-align: left;"
h2Node = ET.SubElement(bodyDivNode,
'h2',
attrib={'style': style})
h2Node.text = "Test Sample Title"
if imgId:
imgDivNode = ET.SubElement(bodyDivNode,
'div',
attrib={'style': 'text-align: left;'})
src = "/website/image?field=datas&"\
"model=ir.attachment&id=%s" % str(imgId)
ET.SubElement(imgDivNode,
'img',
attrib={'class': 'img-thumbnail',
'src': src})
imgDivNode = ET.SubElement(bodyDivNode,
'div',
attrib={'style': 'text-align: left;'})
src = "/website/image/ir.attachment/%s_ccc838d/datas" % str(imgId)
ET.SubElement(imgDivNode,
'img',
attrib={'class': 'img-thumbnail',
'src': src})
arch = ET.tostring(rootNode, encoding='utf-8', xml_declaration=False)
vals = {
'name': pageName,
'type': 'qweb',
'arch': arch,
'page': True,
}
view_id = self.env['ir.ui.view'].create(vals)
return view_id.id
def setUp(self):
super(test_export_help_wizard, self).setUp()
self.pageName = False
self.imgXmlId = False
self.pageTemplate = False
def test_export_help(self):
"""
Export help data
"""
self.createPage(pageName=self.pageName, imgXmlId=self.imgXmlId)
wizardPool = self.env['export.help.wizard']
wizard = wizardPool.create({})
wizard.export_help()
xmlData = base64.decodestring(wizard.data)
parser = ET.XMLParser(remove_blank_text=True)
rootXml = ET.XML(xmlData, parser=parser)
xPath = ".//template[@id='website.%s']" % self.pageName
templateNodeList = rootXml.findall(xPath)
self.assertEqual(len(templateNodeList), 1)
self.assertNotIn("website.", templateNodeList[0].attrib['name'])
if self.imgXmlId:
xPath = ".//record[@id='%s_img_01']" % self.pageName
imgNodeList = rootXml.findall(xPath)
self.assertEqual(len(imgNodeList), 1)
for imgElem in templateNodeList[0].iter('img'):
imgSrc = imgElem.get('src')
if '/ir.attachment/' in imgSrc:
self.assertIn("/ir.attachment/%s_img_02|"
% self.pageName, imgSrc)
else:
self.assertIn("id=%s_img_01" % self.pageName, imgSrc)
if self.pageTemplate:
xPath = ".//template[@id='website.%s_snippet']" % self.pageName
templateNodeList = rootXml.findall(xPath)
self.assertEqual(len(templateNodeList), 1)
self.assertNotIn("website.", templateNodeList[0].attrib['name'])
class test_export_help_with_image(test_export_help_wizard,
SharedSetupTransactionCase):
def setUp(self):
super(test_export_help_with_image, self).setUp()
parameter_model = self.env['ir.config_parameter']
page_prefix = parameter_model.get_param('help_online_page_prefix')
self.pageName = '%stest-page' % page_prefix
self.imgXmlId = 'test_img_1'
class test_export_help_template(test_export_help_wizard,
SharedSetupTransactionCase):
def setUp(self):
super(test_export_help_template, self).setUp()
parameter_model = self.env['ir.config_parameter']
param = 'help_online_template_prefix'
template_prefix = parameter_model.get_param(param)
self.pageName = '%stest-template' % template_prefix
self.pageTemplate = True

52
help_online/views/export_help_wizard_view.xml

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="export_help_wizard_view">
<field name="name">export.help.wizard.view</field>
<field name="model">export.help.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Export Help Data">
<group colspan="2">
<field name="export_filename"
invisible="1"/>
</group>
<group>
<p>
This wizard allow you to export all QWeb views
related to help online. The result will be an Odoo
data xml file.
</p>
</group>
<group>
<field name="data"
nolabel="1"
readonly="1"
filename="export_filename" />
</group>
<footer>
<span name="go-wizard" attrs="{'invisible': [('export_filename', '!=', False)]}">
<button name="export_help"
string="Export"
type="object"
icon="gtk-execute"
class="oe_highlight" />
or
</span>
<button string="Close" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_export_help_wizard">
<field name="name">Export Help</field>
<field name="res_model">export.help.wizard</field>
<field name="view_id" ref="export_help_wizard_view"/>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="type">ir.actions.act_window</field>
</record>
</data>
</openerp>

15
help_online/views/help_online_view.xml

@ -9,5 +9,20 @@
<script type="text/javascript" src="/help_online/static/src/js/help_online.js"></script> <script type="text/javascript" src="/help_online/static/src/js/help_online.js"></script>
</xpath> </xpath>
</template> </template>
<record model="ir.actions.act_window" id="action_website_pages">
<field name="name">Website Pages</field>
<field name="res_model">ir.ui.view</field>
<field name="search_view_id" ref="view_view_search" />
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{"search_default_website":1}</field>
</record>
<menuitem id="menu_help_main" name="Help Online" groups="help_online_group_writer"/>
<menuitem id="menu_help" name="Help Online" parent="menu_help_main" sequence="90" groups="help_online_group_writer"/>
<menuitem id="menu_help_pages" name="Website Pages" parent="menu_help" sequence="10" action="action_website_pages" groups="help_online_group_writer"/>
<menuitem id="menu_help_import" name="Import Help Online" parent="menu_help" sequence="20" action="action_import_help_wizard" groups="help_online_group_writer"/>
<menuitem id="menu_help_export" name="Export Help Online" parent="menu_help" sequence="30" action="action_export_help_wizard" groups="help_online_group_writer"/>
</data> </data>
</openerp> </openerp>

46
help_online/views/import_help_wizard_view.xml

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="import_help_wizard_view">
<field name="name">import.help.wizard.view</field>
<field name="model">import.help.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Import Help Data">
<group>
<p>
This wizard allow you to import QWeb views
related to help online. The required file format is an Odoo
data xml file.
</p>
</group>
<group>
<field name="source_file"/>
</group>
<footer>
<span name="go-wizard" attrs="{'invisible': [('source_file', '=', False)]}">
<button name="import_help"
string="Import"
type="object"
icon="gtk-execute"
class="oe_highlight" />
or
</span>
<button string="Close" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_import_help_wizard">
<field name="name">Import Help</field>
<field name="res_model">import.help.wizard</field>
<field name="view_id" ref="import_help_wizard_view"/>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="type">ir.actions.act_window</field>
</record>
</data>
</openerp>

28
help_online/views/ir_ui_view_view.xml

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="view_view_search" model="ir.ui.view">
<field name="name">ir.ui.view search (help_online)</field>
<field name="inherit_id" ref="base.view_view_search"/>
<field name="model">ir.ui.view</field>
<field name="arch" type="xml">
<xpath expr="//filter[@string='QWeb']" position="after">
<filter name="website" string="Website Page" domain="[('type', '=', 'qweb'),('page', '=', True)]"/>
</xpath>
</field>
</record>
<record id="view_view_form" model="ir.ui.view">
<field name="name">ir.ui.view form (help_online)</field>
<field name="inherit_id" ref="base.view_view_form"/>
<field name="model">ir.ui.view</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='type']" position="after">
<field name="page" string="Website Page?" readonly="1" attrs="{'invisible': [('type', '!=', 'qweb')]}"/>
</xpath>
</field>
</record>
</data>
</openerp>
Loading…
Cancel
Save