Browse Source

Merge pull request #526 from acsone/10.0-mig-help_online-lmi

[10.0] [MIG] help_online
pull/539/head
Dave Lasley 8 years ago
committed by GitHub
parent
commit
6cb007b609
  1. 69
      help_online/README.rst
  2. 23
      help_online/__init__.py
  3. 52
      help_online/__manifest__.py
  4. 22
      help_online/controllers/__init__.py
  5. 25
      help_online/controllers/help_online_controllers.py
  6. 4
      help_online/data/ir_config_parameter_data.xml
  7. 24
      help_online/models/__init__.py
  8. 37
      help_online/models/help_online.py
  9. 49
      help_online/models/import_help_wizard.py
  10. 42
      help_online/models/ir_model.py
  11. 32
      help_online/security/help_online_groups.xml
  12. 58
      help_online/security/help_online_rules.xml
  13. BIN
      help_online/static/description/icon.png
  14. 11
      help_online/static/src/css/help_online.css
  15. 167
      help_online/static/src/js/help_online.js
  16. 21
      help_online/static/src/js/website_help_online.editor.js
  17. 43
      help_online/static/src/xml/help_online.xml
  18. 30
      help_online/tests/__init__.py
  19. 100
      help_online/tests/common.py
  20. 4
      help_online/tests/data/help_test_data.xml
  21. 125
      help_online/tests/test_export_help_wizard.py
  22. 59
      help_online/tests/test_help_online.py
  23. 59
      help_online/tests/test_import_help_wizard.py
  24. 52
      help_online/views/export_help_wizard_view.xml
  25. 46
      help_online/views/help_online_view.xml
  26. 46
      help_online/views/import_help_wizard_view.xml
  27. 48
      help_online/views/ir_ui_view_view.xml
  28. 12
      help_online/views/website_help_online.xml
  29. 6
      help_online/wizards/__init__.py
  30. 241
      help_online/wizards/export_help_wizard.py
  31. 50
      help_online/wizards/export_help_wizard_view.xml
  32. 53
      help_online/wizards/import_help_wizard.py
  33. 43
      help_online/wizards/import_help_wizard_view.xml
  34. 1
      setup/help_online/odoo/__init__.py
  35. 1
      setup/help_online/odoo/addons/__init__.py
  36. 1
      setup/help_online/odoo/addons/help_online
  37. 6
      setup/help_online/setup.py

69
help_online/README.rst

@ -0,0 +1,69 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
===========
Help Online
===========
This module allows the creation of an online help available from the lists
and forms in Odoo.
When loading a view, the module generates a button allowing access to an help
page for the related model if the page exists and the user is member of the
group 'Help reader'. If the page doesn't exist and the user is member of
the group 'Help writer', the module generate a button allowing the creation an
help page.
The help pages are created and managed via the website Module.
Note: When updating the page prefix parameters, the record rules must be
adapted.
Usage
=====
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/162/10.0
Known issues / Roadmap
======================
Even if the generated urls to the documentation contains an anchor (website/hel-xx#view_type),
it's no more possible to insert/edit anchors elements into the website since this functionnality is not supported
by the new html editor in Odoo 10.0 (summernote).
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/web/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Laurent Mignon <laurent.mignon@acsone.eu>
* Jonathan Nemry <jonathan.nemry@acsone.eu>
* Cédric Pigeon <cedric.pigeon@acsone.eu>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

23
help_online/__init__.py

@ -1,22 +1,7 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Authors: Nemry Jonathan
# 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/>.
#
##############################################################################
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import controllers
from . import models
from . import wizards

52
help_online/__manifest__.py

@ -1,65 +1,29 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Authors: Nemry Jonathan
# 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/>.
#
##############################################################################
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Help Online',
'version': '8.0.1.0.0',
'version': '10.0.1.0.0',
'author': "ACSONE SA/NV,Odoo Community Association (OCA)",
'maintainer': 'ACSONE SA/NV',
'website': 'http://www.acsone.eu',
'license': 'AGPL-3',
'category': 'Documentation',
'depends': [
'base',
'website',
],
'description': """
Help Online
===========
This module allows the creation of an online help available from the lists
and forms in Odoo.
When loading a view, the module generates a button allowing access to an help
page for the related model if the page exists and the user is member of the
group 'Help reader'. If the page doesn't exist and the user is member of
the group 'Help writer', the module generate a button allowing the creation an
help page.
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': [
'security/help_online_groups.xml',
'security/help_online_rules.xml',
'views/export_help_wizard_view.xml',
'views/import_help_wizard_view.xml',
'wizards/export_help_wizard_view.xml',
'wizards/import_help_wizard_view.xml',
'views/ir_ui_view_view.xml',
'views/help_online_view.xml',
'views/website_help_online.xml',
'data/ir_config_parameter_data.xml',
],
'qweb': [
'static/src/xml/help_online.xml',
],
'installable': False,
'auto_install': False,
'installable': True,
}

22
help_online/controllers/__init__.py

@ -1,21 +1,5 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Authors: Laurent Mignon
# 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/>.
#
##############################################################################
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from .import help_online_controllers

25
help_online/controllers/help_online_controllers.py

@ -1,26 +1,9 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Authors: Laurent Mignon
# 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/>.
#
##############################################################################
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import openerp.http as http
from openerp.http import request
import odoo.http as http
from odoo.http import request
class HelpOnlineController(http.Controller):

4
help_online/data/ir_config_parameter_data.xml

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8' ?>
<openerp>
<odoo>
<data noupdate="1">
<record id="help_online_autobackup_path" model="ir.config_parameter">
@ -22,4 +22,4 @@
</record>
</data>
</openerp>
</odoo>

24
help_online/models/__init__.py

@ -1,24 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Authors: Nemry Jonathan
# 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/>.
#
##############################################################################
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import help_online
from . import export_help_wizard
from . import import_help_wizard
from . import ir_model

37
help_online/models/help_online.py

@ -1,25 +1,9 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Authors: Laurent Mignon
# 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, exceptions
from openerp.tools.translate import _
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, exceptions
from odoo.tools.translate import _
class HelpOnline(models.TransientModel):
@ -34,9 +18,9 @@ class HelpOnline(models.TransientModel):
name = '%s-%s' % (page_prefix, model.replace('.', '-'))
return name
def page_exists(self, name):
website_model = self.env['website']
return website_model.page_exists(name)
def get_existing_pages(self, name, limit=None):
website = self.env['website']
return website.search_pages(needle=name, limit=limit)
def get_page_url(self, model, view_type, domain=None, context=None):
user_model = self.env['res.users']
@ -48,8 +32,9 @@ class HelpOnline(models.TransientModel):
if res:
description = res[0][1]
name = self._get_view_name(model, view_type, domain, context)
if self.page_exists(name):
url = '/page/%s' % name
pages = self.get_existing_pages(name, limit=1)
if pages:
url = pages[0]['loc']
if view_type:
url = url + '#' + view_type
title = _('Help on %s') % description

49
help_online/models/import_help_wizard.py

@ -1,49 +0,0 @@
# -*- 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)

42
help_online/models/ir_model.py

@ -1,29 +1,13 @@
# -*- 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
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, api
from lxml import etree as ET
class ir_model_data(models.Model):
class IrModelData(models.Model):
_inherit = 'ir.model.data'
@api.model
@ -38,14 +22,14 @@ class ir_model_data(models.Model):
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)
return super(IrModelData, 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)

32
help_online/security/help_online_groups.xml

@ -1,16 +1,16 @@
<openerp>
<data>
<record id="help_online_group_reader" model="res.groups">
<field name="name">Help reader</field>
<field name="category_id" ref="base.module_category_documentation"/>
</record>
<record id="help_online_group_writer" model="res.groups">
<field name="name">Help writer</field>
<field name="category_id" ref="base.module_category_documentation"/>
<field name="implied_ids" eval="[
(4, ref('help_online_group_reader')),
(4, ref('base.group_website_publisher')),
]"/>
</record>
</data>
</openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="help_online_group_reader" model="res.groups">
<field name="name">Help reader</field>
<field name="category_id" ref="base.module_category_documentation"/>
</record>
<record id="help_online_group_writer" model="res.groups">
<field name="name">Help writer</field>
<field name="category_id" ref="base.module_category_documentation"/>
<field name="implied_ids" eval="[
(4, ref('help_online_group_reader')),
(4, ref('website.group_website_publisher')),
]"/>
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
</odoo>

58
help_online/security/help_online_rules.xml

@ -1,32 +1,30 @@
<?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>
<odoo 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>
<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>
</odoo>

BIN
help_online/static/description/icon.png

Before

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

After

Width: 300  |  Height: 300  |  Size: 2.9 KiB

11
help_online/static/src/css/help_online.css

@ -1,12 +1,3 @@
li.oe_help_online_not_found {
a.o_help_online_not_found {
background-color: #df3f3f;
}
.openerp .oe_view_manager .oe_view_manager_switch .oe_list_button_help_online:after {
font-size: 28px;
content: "?";
text-align: center;
margin: 3px auto 4px;
position: relative;
display: inline-block;
}

167
help_online/static/src/js/help_online.js

@ -1,115 +1,86 @@
openerp.help_online = function (instance) {
var QWeb = instance.web.qweb;
var _t = instance.web._t;
var _lt = instance.web._lt;
odoo.define('oca.HelpOnline', function (require) {
"use strict";
instance.web.ListView.include({
load_list: function () {
var self = this;
var add_button = false;
if (!this.$buttons) {
add_button = true;
}
this._super.apply(this, arguments);
this.$buttons.on('click', '.oe_list_button_help_online', function() {
self.do_action({
type: 'ir.actions.act_url',
url: '/partner_mobile',
target: 'self',
});
});
},
});
var core = require('web.core');
var QWeb = core.qweb;
var _t = core._t;
var ViewManager = require('web.ViewManager');
var ControlPanel = require('web.ControlPanel');
var Dialog = require('web.Dialog');
openerp.web.TreeView.include({
view_loading: function(r) {
var ret = this._super(r);
if(! _.isUndefined(this.ViewManager.load_help_buttons)){
this.ViewManager.load_help_buttons();
}
return ret
ControlPanel.include({
start: function(){
this._super.apply(this, arguments);
this._toggle_visibility(true);
this.nodes = _.extend(
this.nodes,
{$help_online_buttons: this.$('.o_help_online_buttons')});
this._toggle_visibility(false);
},
});
openerp.web.ListView.include({
view_loading: function(r) {
var ret = this._super(r);
if(! _.isUndefined(this.ViewManager.load_help_buttons)){
this.ViewManager.load_help_buttons();
}
return ret
},
});
ViewManager.include({
openerp.web.FormView.include({
view_loading: function(r) {
var ret = this._super(r);
if(!_.isUndefined(this.ViewManager.clean_help_buttons)){
this.ViewManager.clean_help_buttons();
/**
* This function render the help button with the informations received
* from the call to the method build_url from the help_online controller
*/
render_help_button: function(url_info){
var $helpButton = $(QWeb.render("HelpOnline.Button", {'view_manager':this, 'url_info': url_info}));
$helpButton.tooltip();
if (url_info.exists === false) {
$helpButton.on('click', function (event) {
var evt = event;
evt.preventDefault();
Dialog.confirm(
self,
_t('Page does not exist. Do you want to create?'),
{confirm_callback: function() {
var form = $("<form></form>");
form.attr({
id : "formform",
// The location given in the link itself
action : evt.target.href,
method : "GET",
// Open in new window/tab
target : evt.target.target
});
$("body").append(form);
$("#formform").submit();
$("#formform").remove();
return false;
}
});
});
}
return ret
return $helpButton;
},
do_show: function (options){
var ret = this._super(options);
if(! _.isUndefined(this.ViewManager.load_help_buttons)){
this.ViewManager.load_help_buttons();
/**
* This function render the help buttons container on the view.
* It should be called after start() by render_view_control_elements.
* @param {control_elements} the list of control elements to display into the ControlPanel
*/
render_help_buttons: function(control_elements){
if (! control_elements.$help_online_buttons){
control_elements.$help_online_buttons = $('<div/>');
}
return ret
},
});
openerp.web.ViewManager.include({
clean_help_buttons:function() {
this.$el.find("div.oe_help_online_buttons").first().remove();
},
load_help_buttons:function() {
var self = this;
this.rpc('/help_online/build_url', {model: this.dataset.model, view_type: this.active_view}).then(function(result) {
self.clean_help_buttons();
this.rpc('/help_online/build_url', {model: this.dataset.model, view_type: this.active_view.type}).then(function(result) {
if (result && ! _.isEmpty(result)) {
self.$helpButtonsEl = $(QWeb.render("HelpOnline.Buttons", {'view_manager':self, 'url_info': result}));
self.$el.find("ul.oe_view_manager_switch.oe_button_group.oe_right").first().before(self.$helpButtonsEl);
self.$helpButtonsEl.find('a.oe_list_button_help_online').tooltip();
if (result.exists === false) {
self.$helpButtonsEl.find('li').addClass('oe_help_online_not_found')
self.$helpButtonsEl.find('a.oe_list_button_help_online').on('click', function (event) {
var evt = event;
evt.preventDefault();
var dialog = new instance.web.Dialog(this, {
title: _t('Confirm'),
buttons: [
{text: _t("Cancel"), click: function() {
this.parents('.modal').modal('hide');
return false;
}
},
{text: _t("Ok"), click: function() {
this.parents('.modal').modal('hide');
var form = $("<form></form>");
form.attr(
{
id : "formform",
// The location given in the link itself
action : evt.target.href,
method : "GET",
// Open in new window/tab
target : evt.target.target
});
$("body").append(form);
$("#formform").submit();
$("#formform").remove();
return false;
}
}
],
}, $('<div/>').text(_t('Page does not exist. Do you want to create?'))).open();
});
}
var $helpButton = self.render_help_button(result);
control_elements.$help_online_buttons = $helpButton;
// update the control panel with the new help button
self.update_control_panel({cp_content: _.extend({}, self.searchview_elements, control_elements)}, {clear: false});
}
});
},
render_view_control_elements: function() {
var control_elements = this._super.apply(this, arguments);
this.render_help_buttons(control_elements);
return control_elements;
},
});
}
});

21
help_online/static/src/js/website_help_online.editor.js

@ -1,21 +0,0 @@
(function () {
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.RTE.include({
_config: function () {
// add anchor button
var config = this._super();
config.plugins = config.plugins.concat(',link');
_.each(config.toolbar, function (tb) {
if (tb.name === 'span'){
tb.items.unshift('Anchor');
}
});
return config;
},
});
})();

43
help_online/static/src/xml/help_online.xml

@ -1,13 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name='HelpOnline.Buttons'>
<div class='oe_help_online_buttons'>
<ul class='oe_view_manager_switch oe_button_group oe_right'>
<li class='oe_i'>
<a class='oe_list_button_help_online'
t-att-title='url_info.title'
t-att-href='url_info.url' target='_blank'></a>
</li>
</ul>
<t t-name='HelpOnline.Button'>
<t t-if='url_info'>
<a t-att-class="'fa fa-question-circle btn btn-icon o_help_online_button ' + (url_info.exists? '': 'o_help_online_not_found')"
t-att-title='url_info.title' t-att-href='url_info.url'
target='_blank'></a>
</t>
</t>
<t t-name='HelpOnline.ButtonsContainer'>
<div class='hidden-xs btn-group btn-group-sm o_help_online_buttons'>
</div>
</t>
</t>
<t t-extend="ControlPanel">
<t t-jquery="div.o_cp_switch_buttons" t-operation="after">
<t t-call='HelpOnline.ButtonsContainer' />
</t>
<t t-jquery="div.oe-cp-switch-buttons" t-operation="after">
<t t-call='HelpOnline.ButtonsContainer' />
</t>
</t>
<t t-extend="X2ManyControlPanel">
<t t-jquery="div.o_cp_pager" t-operation="after">
<t t-call='HelpOnline.ButtonsContainer' />
</t>
<t t-jquery="div.oe-cp-pager" t-operation="after">
<div class="pull-right">
<t t-call='HelpOnline.ButtonsContainer' />
</div>
</t>
</t>
</templates>

30
help_online/tests/__init__.py

@ -1,27 +1,7 @@
# -*- 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 = [
]
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
checks = [
test_export_help_wizard,
]
from . import test_help_online
from . import test_export_help_wizard
from . import test_import_help_wizard

100
help_online/tests/common.py

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright 2016 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
import sys
from lxml import etree as ET
from odoo.tools.convert import convert_xml_import
class TestWizardCommon(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(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})
imgDivNode = ET.SubElement(bodyDivNode,
'div',
attrib={'style': 'text-align: left;'})
src = "/web/image/%s" % 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(TestWizardCommon, self).setUp()
self.pageName = False
self.imgXmlId = False
self.pageTemplate = False
# Loads the data file before
module = sys.modules[self.__class__.__module__]
base_path = os.path.dirname(module.__file__)
for path in self._data_files:
path = path.split('/')
path.insert(0, base_path)
path = os.path.join(*path)
convert_xml_import(self.cr, self._module_ns, path)

4
help_online/tests/data/help_test_data.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data>
<record id="test_img_1" model="ir.attachment">
<field name="datas">iVBORw0KGgoAAAANSUhEUgAAANwAAAAzCAIAAABzKvGBAAAOOUlEQVR42u1beVRU5xWfYcZh3wao
@ -75,4 +75,4 @@ YFBaQ6YYbTbt679Y0nIRIfKXS1d/8J7oqRWRGxO2b92S+NyzK2n3aAwoCf/7jpEfsQAoxU3nESI/
<field name="mimetype">image/png</field>
</record>
</data>
</openerp>
</odoo>

125
help_online/tests/test_export_help_wizard.py

@ -1,105 +1,17 @@
# -*- 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
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
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',)
import odoo.tests.common as common
from .common import TestWizardCommon
_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
class TestExportHelpWizard(TestWizardCommon):
pageName = None
imgXmlId = None
def test_export_help(self):
"""
@ -115,7 +27,7 @@ class test_export_help_wizard(object):
parser = ET.XMLParser(remove_blank_text=True)
rootXml = ET.XML(xmlData, parser=parser)
xPath = ".//template[@id='website.%s']" % self.pageName
xPath = ".//template[@id='__export__.%s']" % self.pageName
templateNodeList = rootXml.findall(xPath)
self.assertEqual(len(templateNodeList), 1)
self.assertNotIn("website.", templateNodeList[0].attrib['name'])
@ -123,7 +35,8 @@ class test_export_help_wizard(object):
if self.imgXmlId:
xPath = ".//record[@id='%s']" % self.imgXmlId
imgNodeList = rootXml.findall(xPath)
self.assertEqual(len(imgNodeList), 2)
self.assertEqual(len(imgNodeList), 1,
'The same image should be exported only once')
for imgElem in templateNodeList[0].iter('img'):
imgSrc = imgElem.get('src')
@ -131,29 +44,27 @@ class test_export_help_wizard(object):
self.assertIn("/ir.attachment/%s|"
% self.imgXmlId, imgSrc)
else:
self.assertIn("id=%s" % self.imgXmlId, imgSrc)
self.assertIn("/web/image/%s" % self.imgXmlId, imgSrc)
if self.pageTemplate:
xPath = ".//template[@id='website.%s_snippet']" % self.pageName
xPath = ".//template[@id='__export__.%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):
class TestExportHelpWithImage(TestExportHelpWizard, common.TransactionCase):
def setUp(self):
super(test_export_help_with_image, self).setUp()
super(TestExportHelpWithImage, 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'
self.imgXmlId = '%s.test_img_1' % self._module_ns
class test_export_help_template(test_export_help_wizard,
SharedSetupTransactionCase):
class TestExportHelpTemplate(TestExportHelpWizard, common.TransactionCase):
def setUp(self):
super(test_export_help_template, self).setUp()
super(TestExportHelpTemplate, self).setUp()
parameter_model = self.env['ir.config_parameter']
param = 'help_online_template_prefix'
template_prefix = parameter_model.get_param(param)

59
help_online/tests/test_help_online.py

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# Copyright 2016 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import mock
import odoo.tests.common as common
from .common import TestWizardCommon
class TestHelpOnline(TestWizardCommon, common.TransactionCase):
def test_get_page_url(self):
model = 'res.partner'
help_online = self.env['help.online']
user = self.env.user
group_writer = self.env.ref('help_online.help_online_group_writer')
group_reader = self.env.ref('help_online.help_online_group_reader')
self.assertTrue(user.has_group('help_online.help_online_group_writer'))
website = self.env['website']
with mock.patch.object(website.__class__,
'search_pages') as search_pages:
# The expected page dosn't exist
search_pages.return_value = []
info = help_online.get_page_url(model, 'form')
self.assertDictEqual(
{'exists': False,
'title': 'Create Help page for Partner',
'url': 'website/add/help-res-partner'}, info,
"If the user is member of help_online_group_writer "
"and the page doesn't exist, the module should return an url "
"to create the page")
# remove user of group writer.
group_writer.write({'users': [(3, self.env.user.id)]})
info = help_online.get_page_url(model, 'form')
self.assertDictEqual(
{}, info,
"If the user is not member of help_online_group_writer "
"and the page doesn't exist, the module should return an "
"empty dict")
# The expected page exists
search_pages.return_value = [{'loc': 'pages/help-res-partner'}]
self.assertTrue(
user.has_group('help_online.help_online_group_reader'))
info = help_online.get_page_url(model, 'form')
self.assertDictEqual(
{'exists': True,
'title': 'Help on Partner',
'url': 'pages/help-res-partner#form'}, info,
"If the user is member of help_online_group_reader "
"and the page exists, the module should return an url "
"to the page")
# remove user from group reader
group_reader.write({'users': [(3, self.env.user.id)]})
info = help_online.get_page_url(model, 'form')
self.assertDictEqual(
{}, info,
"If the user is not member of help_online_group_reader "
"and the page exists, the module should return an empty dict")

59
help_online/tests/test_import_help_wizard.py

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# Copyright 2016 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import base64
import odoo.tests.common as common
from .common import TestWizardCommon
class TestImportHelpWizard(TestWizardCommon, common.TransactionCase):
def setUp(self):
super(TestImportHelpWizard, self).setUp()
self.page_name = "export_import_help"
self.img_xml_id = '%s.test_img_1' % self._module_ns
self.img_name = self.env.ref(self.img_xml_id).name
self.ir_attchement = self.env['ir.attachment']
self.ir_ui_view = self.env['ir.ui.view']
self.export_help_wizard = self.env['export.help.wizard']
self.import_help_wizard = self.env['import.help.wizard']
def _do_check_resources(self, expected=1):
pages = self.ir_ui_view.search([('name', '=', self.page_name)])
self.assertEqual(expected, len(pages))
attachments = self.ir_attchement.search(
[('name', '=', self.img_name)])
self.assertEqual(expected, len(attachments))
def test_import_help(self):
self.createPage(pageName=self.page_name, imgXmlId=self.img_xml_id)
self._do_check_resources()
wizard = self.export_help_wizard.create({})
wizard.export_help()
xmlData = base64.decodestring(wizard.data)
self.env.ref(self.img_xml_id).unlink()
self.ir_ui_view.search([('name', '=', self.page_name)]).unlink()
self._do_check_resources(0)
wizard = self.import_help_wizard.create({
'source_file': base64.encodestring(xmlData)
})
wizard.import_help()
self._do_check_resources()
def test_import_export_help(self):
"""Check that exported data are not ducplicated by export / import
"""
self.createPage(pageName=self.page_name, imgXmlId=self.img_xml_id)
self._do_check_resources()
# export
wizard = self.export_help_wizard.create({})
wizard.export_help()
xmlData = base64.decodestring(wizard.data)
self._do_check_resources()
wizard = self.import_help_wizard.create({
'source_file': base64.encodestring(xmlData)
})
wizard.import_help()
self._do_check_resources()

52
help_online/views/export_help_wizard_view.xml

@ -1,52 +0,0 @@
<?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>

46
help_online/views/help_online_view.xml

@ -1,28 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="help.online.assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/help_online/static/src/css/help_online.css" type="text/css"/>
<script type="text/javascript" src="/help_online/static/src/js/help_online.js"></script>
</xpath>
</template>
<odoo>
<template id="assets_backend" name="help.online.assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/help_online/static/src/css/help_online.css" type="text/css"/>
<script type="text/javascript" src="/help_online/static/src/js/help_online.js"></script>
</xpath>
</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>
<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>
</openerp>
<menuitem id="menu_help_main" name="Help Online" groups="help_online_group_writer" web_icon="help_online,static/description/icon.png"/>
<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"/>
</odoo>

46
help_online/views/import_help_wizard_view.xml

@ -1,46 +0,0 @@
<?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>

48
help_online/views/ir_ui_view_view.xml

@ -1,28 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<odoo>
<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[last()]" position="after">
<filter name="website" string="Website Page" domain="[('type', '=', 'qweb'),('page', '=', True)]"/>
</xpath>
</field>
</record>
<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>
<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>
</odoo>

12
help_online/views/website_help_online.xml

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="assets_editor" inherit_id="website.assets_editor" name="Help online customization">
<xpath expr="." position="inside">
<script type="text/javascript" src="/help_online/static/src/js/website_help_online.editor.js" groups="base.group_website_publisher"></script>
</xpath>
</template>
</data>
</openerp>

6
help_online/wizards/__init__.py

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import export_help_wizard
from . import import_help_wizard

241
help_online/models/export_help_wizard.py → help_online/wizards/export_help_wizard.py

@ -1,31 +1,18 @@
# -*- 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/>.
#
##############################################################################
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
import base64
import time
import copy
import urlparse
from werkzeug.routing import Map, Rule
from lxml import etree as ET
from openerp import models, fields, api, exceptions
from openerp.tools.translate import _
from odoo import models, fields, api, exceptions
from odoo.tools.translate import _
from odoo.addons.web.controllers.main import Binary
from odoo.addons.website.controllers.main import WebsiteBinary
_logger = logging.getLogger(__name__)
@ -43,94 +30,114 @@ class ExportHelpWizard(models.TransientModel):
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, images_reference):
binary = Binary()
websiteBinary = WebsiteBinary()
img_url_map = Map([
Rule('/web/image'),
Rule('/web/image/<string:xmlid>'),
Rule('/web/image/<string:xmlid>/<string:filename>'),
Rule('/web/image/<string:xmlid>/<int:width>x<int:height>'),
Rule('/web/image/<string:xmlid>/<int:width>x<int:height>/'
'<string:filename>'),
Rule('/web/image/<string:model>/<int:id>/<string:field>'),
Rule('/web/image/<string:model>/<int:id>/<string:field>/'
'<string:filename>'),
Rule('/web/image/<string:model>/<int:id>/<string:field>/'
'<int:width>x<int:height>'),
Rule('/web/image/<string:model>/<int:id>/<string:field>/'
'<int:width>x<int:height>/<string:filename>'),
Rule('/web/image/<int:id>'),
Rule('/web/image/<int:id>/<string:filename>'),
Rule('/web/image/<int:id>/<int:width>x<int:height>'),
Rule('/web/image/<int:id>/<int:width>x<int:height>/<string:filename>'),
Rule('/web/image/<int:id>-<string:unique>'),
Rule('/web/image/<int:id>-<string:unique>/<string:filename>'),
Rule('/web/image/<int:id>-<string:unique>/<int:width>x<int:height>'),
Rule('/web/image/<int:id>-<string:unique>/<int:width>x<int:height>'
'/<string:filename>'),
Rule('/website/image'),
Rule('/website/image/<xmlid>'),
Rule('/website/image/<xmlid>/<int:width>x<int:height>'),
Rule('/website/image/<xmlid>/<field>'),
Rule('/website/image/<xmlid>/<field>/<int:width>x<int:height>'),
Rule('/website/image/<model>/<id>/<field>'),
Rule('/website/image/<model>/<id>/<field>/<int:width>x<int:height>')
])
def _manage_images_on_page(self, page_node, data_node, exported_resources):
"""
- Extract images from page and generate a xml node
- Extract images from page and generate an xml node
- Replace db id in url with xml id
"""
def get_attach_id(images_reference,
img_model, img_src, generated_xml_id=False):
attach_id = False
if 'id=' in img_src:
id_pos = img_src.index('id=') + 3
attach_id = img_src[id_pos:]
else:
fragments = img_src.split('ir.attachment/')
attach_id, _ = fragments[1].split('_', 1)
if attach_id in images_reference:
xml_id = images_reference[attach_id]
else:
ir_data = self.env['ir.model.data'].search(
[('model', '=', img_model),
('res_id', '=', attach_id)])
xml_id = generated_xml_id
if ir_data:
xml_id = ir_data[0].name
images_reference[attach_id] = xml_id
return attach_id, xml_id
def substitute_id_by_xml_id(img_src, attach_id, xml_id):
new_src = False
if 'id=' in img_src:
new_src = img_src.replace(attach_id, xml_id)
else:
fragments = img_src.split('ir.attachment/')
_, trail = fragments[1].split('_', 1)
new_src = "/website/image/ir.attachment/%s|%s" % \
(xml_id, trail)
return new_src
i_img = 0
img_model = 'ir.attachment'
urls = self.img_url_map.bind("dummy.org", "/")
for img_elem in page_node.iter('img'):
img_src = img_elem.get('src')
if img_model in img_src:
i_img += 1
generated_xml_id = "%s_img_%s" % \
(page_node.attrib['name'], str(i_img).rjust(2, '0'))
attach_id, xml_id = get_attach_id(images_reference,
img_model,
img_src,
generated_xml_id)
new_src = substitute_id_by_xml_id(img_src, attach_id, xml_id)
if not attach_id:
continue
image = self.env[img_model].browse(int(attach_id))
if not image:
continue
parse_result = urlparse.urlparse(img_src)
path = parse_result.path
query_args = parse_result.query
if urls.test(parse_result.path, "GET"):
endpoint, kwargs = urls.match(path, "GET",
query_args=query_args)
kwargs.update(dict(urlparse.parse_qsl(query_args)))
image = None
# get the binary object
xml_id = kwargs.get('xmlid')
if xml_id:
image = self.env.ref(xml_id, False)
else:
_id = kwargs.get('id')
model = kwargs.get('model', 'ir.attachment')
if _id and model:
_id, _, unique = str(_id).partition('_')
image = self.env[model].browse(int(_id))
if (not image or
not image.exists() or
image._name != img_model):
raise exceptions.UserError(
_('Only images from ir.attachment are supported when '
'exporting help pages'))
exported_data = image.export_data(
['id',
'datas',
'datas_fname',
'name',
'res_model',
'mimetype'],
raw_data=False)['datas'][0]
xml_id = exported_data[0]
new_src = '/web/image/%s' % xml_id
img_elem.attrib['src'] = new_src
img_node = ET.SubElement(data_node,
'record',
attrib={'id': xml_id,
'model': img_model})
if xml_id in exported_resources:
continue
img_node = ET.SubElement(
data_node,
'record',
attrib={'id': xml_id,
'model': image._name})
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'datas'})
field_node.text = str(image.datas)
field_node.text = str(exported_data[1])
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'datas_fname'})
field_node.text = image.datas_fname
field_node.text = exported_data[2]
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'name'})
field_node.text = image.name
field_node.text = exported_data[3]
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'res_model'})
field_node.text = image.res_model
field_node.text = exported_data[4]
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'mimetype'})
field_node.text = image.mimetype
field_node.text = exported_data[5]
data_node.append(img_node)
exported_resources.add(xml_id)
def _clean_href_urls(self, page_node, page_prefix, template_prefix):
"""
@ -216,39 +223,71 @@ class ExportHelpWizard(models.TransientModel):
('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')
ir_ui_views = self.env['ir.ui.view'].search(domain, order='name')
xml_to_export = ET.Element('odoo')
data_node = ET.SubElement(xml_to_export, 'data')
images_reference = {}
for view_data in view_data_list:
exported_resources = set()
for ir_ui_view in ir_ui_views:
parser = ET.XMLParser(remove_blank_text=True)
root = ET.XML(view_data['arch'], parser=parser)
root = ET.XML(ir_ui_view.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
xml_id = self._get_ir_ui_view_xml_id(
ir_ui_view, root.attrib.pop('t-name'))
root.attrib['name'] = ir_ui_view.name.replace('website.', '')
root.attrib['id'] = xml_id
root.attrib['page'] = 'True'
self._manage_images_on_page(root, data_node, images_reference)
self._manage_images_on_page(root, data_node, exported_resources)
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,
xml_id,
template_prefix)
data_node.append(snippet)
if len(view_data_list) > 0:
if len(ir_ui_views) > 0:
return ET.tostring(xml_to_export, encoding='utf-8',
xml_declaration=True,
pretty_print=True)
else:
return False
@api.model
def _get_ir_ui_view_xml_id(self, ir_ui_view, template_name):
"""This method check if an xml_id exists for the given ir.ui.view
If no xml_id exists, a new one is created with template name as
value to ensure that the import of the generated file will update
the existing view in place of creating new copies.
"""
ir_model_data = self.sudo().env['ir.model.data']
data = ir_model_data.search([('model', '=', ir_ui_view._name),
('res_id', '=', ir_ui_view.id)])
if data:
if data[0].module:
return '%s.%s' % (data[0].module, data[0].name)
else:
return data[0].name
else:
module, name = template_name.split('.')
# always use __export__ as module by convention to
# avoid the removal by odoo of the exported pages on system
# update (-u )
module = "__export__"
postfix = ir_model_data.search_count(
[('module', '=', module),
('name', 'like', name)])
if postfix:
name = '%s_%s' % (name, postfix)
ir_model_data.create({
'model': ir_ui_view._name,
'res_id': ir_ui_view.id,
'module': module,
'name': name,
})
return module + '.' + name
@api.multi
def export_help(self):
"""

50
help_online/wizards/export_help_wizard_view.xml

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<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>
</odoo>

53
help_online/wizards/import_help_wizard.py

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import base64
from cStringIO import StringIO
from lxml import etree
import logging
import os
from odoo import api, fields, models
from odoo.tools import convert, misc
from odoo.tools.config import config
_logger = logging.getLogger(__name__)
class XmlImport(convert.xml_import):
"""Override base xml_import to be able to import record with an exported
xml_id ('__export__.XXX-XXX')
"""
def _test_xml_id(self, xml_id):
if '.' in xml_id:
module, _id = xml_id.split('.')
if module == '__export__':
return True
super(XmlImport, self)._test_xml_id(xml_id)
class ImportHelpWizard(models.TransientModel):
_name = "import.help.wizard"
source_file = fields.Binary('Source File')
@api.multi
def import_help(self):
for this in self:
xmlfile = StringIO(base64.decodestring(this.source_file))
doc = etree.parse(xmlfile)
relaxng = etree.RelaxNG(
etree.parse(
os.path.join(config['root_path'], 'import_xml.rng')))
try:
relaxng.assert_(doc)
except Exception:
_logger.info('The XML file does not fit the required schema !',
exc_info=True)
_logger.info(misc.ustr(relaxng.error_log.last_error))
raise
obj = XmlImport(self.env.cr, self._module, idref={}, mode='init',
report=None, noupdate=False, xml_filename=None)
obj.parse(doc.getroot(), mode='init')

43
help_online/wizards/import_help_wizard_view.xml

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<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>
</odoo>

1
setup/help_online/odoo/__init__.py

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

1
setup/help_online/odoo/addons/__init__.py

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

1
setup/help_online/odoo/addons/help_online

@ -0,0 +1 @@
../../../../help_online

6
setup/help_online/setup.py

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
Loading…
Cancel
Save