You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

339 lines
14 KiB

# 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
from urllib.parse import urlparse
from urllib.parse import parse_qsl
from werkzeug.routing import Map, Rule
from lxml import etree as ET
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__)
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)
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 an xml node
- Replace db id in url with xml id
"""
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')
parse_result = 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(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
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(exported_data[1])
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'datas_fname'})
field_node.text = exported_data[2]
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'name'})
field_node.text = exported_data[3]
field_node = ET.SubElement(img_node,
'field',
attrib={'name': 'res_model'})
field_node.text = exported_data[4]
field_node = ET.SubElement(img_node,
'field',
attrib={'name': '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):
"""
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_ids', '!=', False),
'|',
('name', 'like', '%s%%' % page_prefix),
('name', 'like', '%s%%' % template_prefix)]
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')
exported_resources = set()
for ir_ui_view in ir_ui_views:
parser = ET.XMLParser(remove_blank_text=True)
root = ET.XML(ir_ui_view.arch, parser=parser)
root.tag = 'template'
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_ids'] = 'True'
root.attrib['key'] = ir_ui_view.key
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,
xml_id,
template_prefix)
data_node.append(snippet)
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):
"""
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))