diff --git a/help_online/__openerp__.py b/help_online/__openerp__.py index c27f0e55..832d3be1 100644 --- a/help_online/__openerp__.py +++ b/help_online/__openerp__.py @@ -3,11 +3,19 @@ # # Authors: Nemry Jonathan # 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. +# 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 @@ -45,9 +53,13 @@ help page. The help pages are created and managed via the website Module. """, 'data': [ + 'data/help_auto_backup.xml', # must always be the first 'security/help_online_groups.xml', 'views/help_online_view.xml', 'views/website_help_online.xml', + 'views/ir_ui_view_view.xml', + 'views/export_help_wizard_view.xml', + 'data/ir_config_parameter_data.xml', ], 'qweb': [ 'static/src/xml/help_online.xml', diff --git a/help_online/data/help_auto_backup.xml b/help_online/data/help_auto_backup.xml new file mode 100644 index 00000000..ac6dcd57 --- /dev/null +++ b/help_online/data/help_auto_backup.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/help_online/data/ir_config_parameter_data.xml b/help_online/data/ir_config_parameter_data.xml new file mode 100644 index 00000000..94f859fa --- /dev/null +++ b/help_online/data/ir_config_parameter_data.xml @@ -0,0 +1,25 @@ + + + + + + help_online_autobackup_path + False + + + + + + help_online_page_prefix + help + + + + + + help_online_template_prefix + help-template + + + + \ No newline at end of file diff --git a/help_online/models/__init__.py b/help_online/models/__init__.py index fe7c6a0f..fd36eaa5 100644 --- a/help_online/models/__init__.py +++ b/help_online/models/__init__.py @@ -3,11 +3,19 @@ # # Authors: Nemry Jonathan # 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. +# 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 @@ -19,3 +27,5 @@ # ############################################################################## from . import help_online +from . import export_help_wizard +from . import ir_model diff --git a/help_online/models/export_help_wizard.py b/help_online/models/export_help_wizard.py new file mode 100644 index 00000000..c98ed837 --- /dev/null +++ b/help_online/models/export_help_wizard.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Authors: Cédric Pigeon +# 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 . +# +############################################################################## +import logging +import base64 +import time +import copy + +from lxml import etree as ET +from xml.dom import minidom as minidom +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(): + new_src = False + attach_id = False + 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')) + img_src = img_elem.get('src') + + new_src, attach_id = substitute_id_by_xml_id() + + 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 not page_url in href and not template_url in href: + continue + elif page_url in href and not template_url in href: + pass + elif not page_url 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'}) + img = 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: + rough_string = ET.tostring(xml_to_export, encoding='utf-8', + xml_declaration=True) + reparsed = minidom.parseString(rough_string) + return reparsed.toprettyxml(indent=" ", encoding='utf-8') + 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': 'Help Online Export', + '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)) diff --git a/help_online/models/help_online.py b/help_online/models/help_online.py index 15e41a70..5a076cd6 100644 --- a/help_online/models/help_online.py +++ b/help_online/models/help_online.py @@ -3,11 +3,19 @@ # # Authors: Laurent Mignon # 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. +# 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 @@ -18,15 +26,20 @@ # along with this program. If not, see . # ############################################################################## -from openerp.osv import orm +from openerp import models, exceptions from openerp.tools.translate import _ -class HelpOnline(orm.TransientModel): +class HelpOnline(models.TransientModel): _name = 'help.online' 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 def page_exists(self, name): @@ -40,7 +53,7 @@ class HelpOnline(orm.TransientModel): ir_model = self.env['ir.model'] description = self.env[model]._description res = ir_model.name_search(model, operator='=') - if res: + if(res): description = res[0][1] name = self._get_view_name(model, view_type, domain, context) if self.page_exists(name): diff --git a/help_online/models/ir_model.py b/help_online/models/ir_model.py new file mode 100644 index 00000000..8cbccf69 --- /dev/null +++ b/help_online/models/ir_model.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Authors: Cédric Pigeon +# 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 . +# +############################################################################## +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) diff --git a/help_online/static/description/icon.png b/help_online/static/description/icon.png new file mode 100644 index 00000000..ab8d478d Binary files /dev/null and b/help_online/static/description/icon.png differ diff --git a/help_online/static/description/index.html b/help_online/static/description/index.html index e2edb627..7cab8f1b 100644 --- a/help_online/static/description/index.html +++ b/help_online/static/description/index.html @@ -12,5 +12,6 @@ help_online_create_page.png

The help pages are created and managed via the website Module.


+

If you want to export your work, you simply have to use the export wizard through the settings menu.

diff --git a/help_online/static/src/img/snippet/snippet_thumbs.png b/help_online/static/src/img/snippet/snippet_thumbs.png new file mode 100644 index 00000000..4d5c2d89 Binary files /dev/null and b/help_online/static/src/img/snippet/snippet_thumbs.png differ diff --git a/help_online/tests/__init__.py b/help_online/tests/__init__.py new file mode 100644 index 00000000..6085ce18 --- /dev/null +++ b/help_online/tests/__init__.py @@ -0,0 +1,35 @@ +# -*- 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 . +# +############################################################################## +from . import test_export_help_wizard + +fast_suite = [ +] + +checks = [ + test_export_help_wizard, +] diff --git a/help_online/tests/data/help_test_data.xml b/help_online/tests/data/help_test_data.xml new file mode 100644 index 00000000..3d939d21 --- /dev/null +++ b/help_online/tests/data/help_test_data.xml @@ -0,0 +1,78 @@ + + + + + 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 + + image + test.png + test.png + ir.ui.view + image/png + + + \ No newline at end of file diff --git a/help_online/tests/test_export_help_wizard.py b/help_online/tests/test_export_help_wizard.py new file mode 100644 index 00000000..6494f121 --- /dev/null +++ b/help_online/tests/test_export_help_wizard.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Authors: Cédric Pigeon +# 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 . +# +############################################################################## +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) + imgNode = 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) + imgNode = 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'] + template_prefix = parameter_model.get_param( + 'help_online_template_prefix') + self.pageName = '%stest-template' % template_prefix + self.pageTemplate = True diff --git a/help_online/views/export_help_wizard_view.xml b/help_online/views/export_help_wizard_view.xml new file mode 100644 index 00000000..7e5235df --- /dev/null +++ b/help_online/views/export_help_wizard_view.xml @@ -0,0 +1,56 @@ + + + + + export.help.wizard.view + export.help.wizard + form + +
+ + + + +

+ This wizard allow you to export all QWeb views + related to help online. The result will be an Odoo + data xml file. +

+
+ + + +
+ +
+
+
+
+ + + Export Help + export.help.wizard + + form + form + new + ir.actions.act_window + + + + + +
+
\ No newline at end of file diff --git a/help_online/views/ir_ui_view_view.xml b/help_online/views/ir_ui_view_view.xml new file mode 100644 index 00000000..85fc7783 --- /dev/null +++ b/help_online/views/ir_ui_view_view.xml @@ -0,0 +1,28 @@ + + + + + + ir.ui.view search (help_online) + + ir.ui.view + + + + + + + + + ir.ui.view form (help_online) + + ir.ui.view + + + + + + + + +