[MIG] web_editor: migrate to new api
This commit is contained in:
committed by
Thibault Delavallée
parent
65fbeb21f6
commit
a32c1d3c8d
@@ -1,4 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import controllers
|
||||
import models
|
||||
import tests
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Web Editor',
|
||||
'category': 'Hidden',
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import main
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp.http import request, STATIC_CACHE
|
||||
from openerp import http
|
||||
import json
|
||||
import io
|
||||
from PIL import Image, ImageFont, ImageDraw
|
||||
from openerp import tools
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import cStringIO
|
||||
import werkzeug.wrappers
|
||||
import time
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import werkzeug.wrappers
|
||||
from PIL import Image, ImageFont, ImageDraw
|
||||
|
||||
from odoo.http import request
|
||||
from odoo import http, tools
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -25,8 +28,6 @@ class Web_Editor(http.Controller):
|
||||
#------------------------------------------------------
|
||||
@http.route('/web_editor/field/html', type='http', auth="user")
|
||||
def FieldTextHtml(self, model=None, res_id=None, field=None, callback=None, **kwargs):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
|
||||
kwargs.update(
|
||||
model=model,
|
||||
res_id=res_id,
|
||||
@@ -39,17 +40,16 @@ class Web_Editor(http.Controller):
|
||||
kwargs[k] = int(kwargs[k])
|
||||
|
||||
trans = dict(
|
||||
lang=kwargs.get('lang', context.get('lang')),
|
||||
lang=kwargs.get('lang', request.env.context.get('lang')),
|
||||
translatable=kwargs.get('translatable'),
|
||||
edit_translations=kwargs.get('edit_translations'),
|
||||
editable=kwargs.get('enable_editor'))
|
||||
|
||||
context.update(trans)
|
||||
kwargs.update(trans)
|
||||
|
||||
record = None
|
||||
if model and kwargs.get('res_id'):
|
||||
record = request.registry[model].browse(cr, uid, kwargs.get('res_id'), context)
|
||||
record = request.env[model].with_context(trans).browse(kwargs.get('res_id'))
|
||||
|
||||
kwargs.update(content=record and getattr(record, field) or "")
|
||||
|
||||
@@ -141,23 +141,23 @@ class Web_Editor(http.Controller):
|
||||
# the upload argument doesn't allow us to access the files if more than
|
||||
# one file is uploaded, as upload references the first file
|
||||
# therefore we have to recover the files from the request object
|
||||
Attachments = request.registry['ir.attachment'] # registry for the attachment table
|
||||
Attachments = request.env['ir.attachment'] # registry for the attachment table
|
||||
|
||||
uploads = []
|
||||
message = None
|
||||
if not upload: # no image provided, storing the link and the image name
|
||||
name = url.split("/").pop() # recover filename
|
||||
attachment_id = Attachments.create(request.cr, request.uid, {
|
||||
attachment = Attachments.create({
|
||||
'name': name,
|
||||
'type': 'url',
|
||||
'url': url,
|
||||
'public': True,
|
||||
'res_model': 'ir.ui.view',
|
||||
}, request.context)
|
||||
uploads += Attachments.read(request.cr, request.uid, [attachment_id], ['name', 'mimetype', 'checksum', 'url'], request.context)
|
||||
})
|
||||
uploads += attachment.read(['name', 'mimetype', 'checksum', 'url'])
|
||||
else: # images provided
|
||||
try:
|
||||
attachment_ids = []
|
||||
attachments = request.env['ir.attachment']
|
||||
for c_file in request.httprequest.files.getlist('upload'):
|
||||
data = c_file.read()
|
||||
try:
|
||||
@@ -172,15 +172,15 @@ class Web_Editor(http.Controller):
|
||||
except IOError, e:
|
||||
pass
|
||||
|
||||
attachment_id = Attachments.create(request.cr, request.uid, {
|
||||
attachment = Attachments.create({
|
||||
'name': c_file.filename,
|
||||
'datas': data.encode('base64'),
|
||||
'datas_fname': c_file.filename,
|
||||
'public': True,
|
||||
'res_model': 'ir.ui.view',
|
||||
}, request.context)
|
||||
attachment_ids.append(attachment_id)
|
||||
uploads += Attachments.read(request.cr, request.uid, attachment_ids, ['name', 'mimetype', 'checksum', 'url'], request.context)
|
||||
})
|
||||
attachments += attachment
|
||||
uploads += attachments.read(['name', 'mimetype', 'checksum', 'url'])
|
||||
except Exception, e:
|
||||
logger.exception("Failed to upload image to attachment")
|
||||
message = unicode(e)
|
||||
@@ -199,25 +199,26 @@ class Web_Editor(http.Controller):
|
||||
Returns a dict mapping attachments which would not be removed (if any)
|
||||
mapped to the views preventing their removal
|
||||
"""
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
Attachment = request.registry['ir.attachment']
|
||||
Views = request.registry['ir.ui.view']
|
||||
Attachment = attachments_to_remove = request.env['ir.attachment']
|
||||
Views = request.env['ir.ui.view']
|
||||
|
||||
attachments_to_remove = []
|
||||
# views blocking removal of the attachment
|
||||
removal_blocked_by = {}
|
||||
|
||||
for attachment in Attachment.browse(cr, uid, ids, context=context):
|
||||
for attachment in Attachment.browse(ids):
|
||||
# in-document URLs are html-escaped, a straight search will not
|
||||
# find them
|
||||
url = tools.html_escape(attachment.local_url)
|
||||
ids = Views.search(cr, uid, ["|", ('arch_db', 'like', '"%s"' % url), ('arch_db', 'like', "'%s'" % url)], context=context)
|
||||
views = Views.search([
|
||||
"|",
|
||||
('arch_db', 'like', '"%s"' % url),
|
||||
('arch_db', 'like', "'%s'" % url)
|
||||
])
|
||||
|
||||
if ids:
|
||||
removal_blocked_by[attachment.id] = Views.read(
|
||||
cr, uid, ids, ['name'], context=context)
|
||||
if views:
|
||||
removal_blocked_by[attachment.id] = views.read(['name'])
|
||||
else:
|
||||
attachments_to_remove.append(attachment.id)
|
||||
attachments_to_remove += attachment
|
||||
if attachments_to_remove:
|
||||
Attachment.unlink(cr, uid, attachments_to_remove, context=context)
|
||||
attachments_to_remove.unlink()
|
||||
return removal_blocked_by
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import ir_attachment
|
||||
import ir_qweb
|
||||
import ir_ui_view
|
||||
|
||||
@@ -1,36 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import openerp
|
||||
from openerp.osv import osv, fields
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import contextlib
|
||||
from sys import maxint
|
||||
from openerp.http import request
|
||||
import datetime
|
||||
import hashlib
|
||||
import time
|
||||
import os
|
||||
|
||||
from openerp.tools import html_escape as escape, ustr, image_resize_and_sharpen, image_save_for_web
|
||||
from PIL import Image
|
||||
import cStringIO
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
from odoo import api, models, fields
|
||||
|
||||
|
||||
class ir_attachment(osv.osv):
|
||||
class IrAttachment(models.Model):
|
||||
|
||||
_inherit = "ir.attachment"
|
||||
|
||||
def _local_url_get(self, cr, uid, ids, name, arg, context=None):
|
||||
result = {}
|
||||
for attach in self.browse(cr, uid, ids, context=context):
|
||||
if attach.url:
|
||||
result[attach.id] = attach.url
|
||||
else:
|
||||
result[attach.id] = '/web/image/%s?unique=%s' % (attach.id, attach.checksum)
|
||||
return result
|
||||
local_url = fields.Char("Attachment URL", compute='_compute_local_url')
|
||||
|
||||
_columns = {
|
||||
'local_url': fields.function(_local_url_get, string="Attachment URL", type='char'),
|
||||
}
|
||||
@api.one
|
||||
def _compute_local_url(self):
|
||||
if self.url:
|
||||
self.local_url = self.url
|
||||
else:
|
||||
self.local_url = '/web/image/%s?unique=%s' % (self.id, self.checksum)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp.osv import orm
|
||||
from openerp.http import request
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class ir_http(orm.AbstractModel):
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def _dispatch(self):
|
||||
@@ -13,4 +15,4 @@ class ir_http(orm.AbstractModel):
|
||||
request.context['edit_translations'] = True
|
||||
if request.context.get('lang') != "en_US" and 'translatable' not in request.context:
|
||||
request.context['translatable'] = True
|
||||
return super(ir_http, self)._dispatch()
|
||||
return super(IrHttp, self)._dispatch()
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
"""
|
||||
Web_editor-context rendering needs to add some metadata to rendered and allow to edit fields,
|
||||
as well as render a few fields differently.
|
||||
|
||||
Also, adds methods to convert values back to openerp models.
|
||||
Also, adds methods to convert values back to Odoo models.
|
||||
"""
|
||||
|
||||
import ast
|
||||
@@ -22,22 +24,19 @@ import pytz
|
||||
from dateutil import parser
|
||||
from lxml import etree, html
|
||||
from PIL import Image as I
|
||||
import openerp.modules
|
||||
import odoo.modules
|
||||
|
||||
from odoo import api
|
||||
|
||||
import openerp
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools import ustr, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools import html_escape as escape
|
||||
from openerp.addons.base.ir import ir_qweb
|
||||
from odoo import api, models, fields
|
||||
from odoo.tools import ustr
|
||||
from odoo.tools import html_escape as escape
|
||||
from odoo.addons.base.ir import ir_qweb
|
||||
|
||||
REMOTE_CONNECTION_TIMEOUT = 2.5
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QWeb(orm.AbstractModel):
|
||||
class QWeb(models.AbstractModel):
|
||||
""" QWeb object for rendering editor stuff
|
||||
"""
|
||||
_inherit = 'ir.qweb'
|
||||
@@ -69,7 +68,7 @@ class QWeb(orm.AbstractModel):
|
||||
#------------------------------------------------------
|
||||
|
||||
|
||||
class Field(orm.AbstractModel):
|
||||
class Field(models.AbstractModel):
|
||||
_name = 'ir.qweb.field'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
@@ -98,14 +97,14 @@ class Field(orm.AbstractModel):
|
||||
return self.value_from_string(element.text_content().strip())
|
||||
|
||||
|
||||
class Integer(orm.AbstractModel):
|
||||
class Integer(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.integer'
|
||||
_inherit = 'ir.qweb.field.integer'
|
||||
|
||||
value_from_string = int
|
||||
|
||||
|
||||
class Float(orm.AbstractModel):
|
||||
class Float(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.float'
|
||||
_inherit = 'ir.qweb.field.float'
|
||||
|
||||
@@ -117,7 +116,7 @@ class Float(orm.AbstractModel):
|
||||
.replace(lang.decimal_point, '.'))
|
||||
|
||||
|
||||
class ManyToOne(orm.AbstractModel):
|
||||
class ManyToOne(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.many2one'
|
||||
_inherit = 'ir.qweb.field.many2one'
|
||||
|
||||
@@ -146,7 +145,7 @@ class ManyToOne(orm.AbstractModel):
|
||||
return None
|
||||
|
||||
|
||||
class Contact(orm.AbstractModel):
|
||||
class Contact(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.contact'
|
||||
_inherit = 'ir.qweb.field.contact'
|
||||
|
||||
@@ -165,18 +164,7 @@ class Contact(orm.AbstractModel):
|
||||
return node and node.__html__()
|
||||
|
||||
|
||||
def parse_fuzzy(in_format, value):
|
||||
day_first = in_format.find('%d') < in_format.find('%m')
|
||||
|
||||
if '%y' in in_format:
|
||||
year_first = in_format.find('%y') < in_format.find('%d')
|
||||
else:
|
||||
year_first = in_format.find('%Y') < in_format.find('%d')
|
||||
|
||||
return parser.parse(value, dayfirst=day_first, yearfirst=year_first)
|
||||
|
||||
|
||||
class Date(orm.AbstractModel):
|
||||
class Date(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.date'
|
||||
_inherit = 'ir.qweb.field.date'
|
||||
|
||||
@@ -192,11 +180,10 @@ class Date(orm.AbstractModel):
|
||||
if not value:
|
||||
return False
|
||||
|
||||
datetime.datetime.strptime(value, DEFAULT_SERVER_DATE_FORMAT)
|
||||
return value
|
||||
|
||||
|
||||
class DateTime(orm.AbstractModel):
|
||||
class DateTime(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.datetime'
|
||||
_inherit = 'ir.qweb.field.datetime'
|
||||
|
||||
@@ -205,13 +192,11 @@ class DateTime(orm.AbstractModel):
|
||||
attrs = super(DateTime, self).attributes(record, field_name, options, values)
|
||||
value = record[field_name]
|
||||
if isinstance(value, basestring):
|
||||
value = datetime.datetime.strptime(
|
||||
value, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
value = fields.Datetime.from_string(value)
|
||||
if value:
|
||||
# convert from UTC (server timezone) to user timezone
|
||||
value = fields.datetime.context_timestamp(
|
||||
self._cr, self._uid, timestamp=value, context=self.env.context)
|
||||
value = value.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
value = fields.Datetime.context_timestamp(self, timestamp=value)
|
||||
value = fields.Datetime.to_string(value)
|
||||
attrs['data-oe-original'] = value
|
||||
return attrs
|
||||
|
||||
@@ -240,10 +225,10 @@ class DateTime(orm.AbstractModel):
|
||||
exc_info=True)
|
||||
|
||||
# format back to string
|
||||
return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
return fields.Datetime.to_string(dt)
|
||||
|
||||
|
||||
class Text(orm.AbstractModel):
|
||||
class Text(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.text'
|
||||
_inherit = 'ir.qweb.field.text'
|
||||
|
||||
@@ -252,7 +237,7 @@ class Text(orm.AbstractModel):
|
||||
return html_to_text(element)
|
||||
|
||||
|
||||
class Selection(orm.AbstractModel):
|
||||
class Selection(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.selection'
|
||||
_inherit = 'ir.qweb.field.selection'
|
||||
|
||||
@@ -270,7 +255,7 @@ class Selection(orm.AbstractModel):
|
||||
value, selection))
|
||||
|
||||
|
||||
class HTML(orm.AbstractModel):
|
||||
class HTML(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.html'
|
||||
_inherit = 'ir.qweb.field.html'
|
||||
|
||||
@@ -284,7 +269,7 @@ class HTML(orm.AbstractModel):
|
||||
return '\n'.join(content)
|
||||
|
||||
|
||||
class Image(orm.AbstractModel):
|
||||
class Image(models.AbstractModel):
|
||||
"""
|
||||
Widget options:
|
||||
|
||||
@@ -361,7 +346,7 @@ class Image(orm.AbstractModel):
|
||||
if sep and sep != '/':
|
||||
rest.replace(sep, '/')
|
||||
|
||||
path = openerp.modules.get_module_resource(
|
||||
path = odoo.modules.get_module_resource(
|
||||
match.group('module'), 'static', *(rest.split('/')))
|
||||
|
||||
if not path:
|
||||
@@ -403,7 +388,7 @@ class Image(orm.AbstractModel):
|
||||
return out.getvalue().encode('base64')
|
||||
|
||||
|
||||
class Monetary(orm.AbstractModel):
|
||||
class Monetary(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.monetary'
|
||||
_inherit = 'ir.qweb.field.monetary'
|
||||
|
||||
@@ -417,7 +402,7 @@ class Monetary(orm.AbstractModel):
|
||||
.replace(lang.decimal_point, '.'))
|
||||
|
||||
|
||||
class Duration(orm.AbstractModel):
|
||||
class Duration(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.duration'
|
||||
_inherit = 'ir.qweb.field.duration'
|
||||
|
||||
@@ -435,14 +420,14 @@ class Duration(orm.AbstractModel):
|
||||
return float(value)
|
||||
|
||||
|
||||
class RelativeDatetime(orm.AbstractModel):
|
||||
class RelativeDatetime(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.relative'
|
||||
_inherit = 'ir.qweb.field.relative'
|
||||
|
||||
# get formatting from ir.qweb.field.relative but edition/save from datetime
|
||||
|
||||
|
||||
class QwebView(orm.AbstractModel):
|
||||
class QwebView(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.qweb'
|
||||
_inherit = 'ir.qweb.field.qweb'
|
||||
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from openerp import models, api
|
||||
from openerp.tools.translate import encode, xml_translate, html_translate
|
||||
from odoo import models, api
|
||||
from odoo.tools.translate import encode, xml_translate, html_translate
|
||||
|
||||
|
||||
def edit_translation_mapping(data):
|
||||
data = dict(data, model=data['name'].partition(',')[0])
|
||||
return '<span data-oe-model="%(model)s" data-oe-translation-id="%(id)s" data-oe-translation-state="%(state)s">%(value)s</span>' % data
|
||||
|
||||
class ir_translation(models.Model):
|
||||
|
||||
class IrTranslation(models.Model):
|
||||
_inherit = 'ir.translation'
|
||||
|
||||
@api.model
|
||||
@@ -18,7 +20,7 @@ class ir_translation(models.Model):
|
||||
if self._context.get('edit_translations'):
|
||||
self.insert_missing(field, records)
|
||||
return edit_translation_mapping
|
||||
return super(ir_translation, self)._get_terms_mapping(field, records)
|
||||
return super(IrTranslation, self)._get_terms_mapping(field, records)
|
||||
|
||||
@api.multi
|
||||
def save_html(self, value):
|
||||
|
||||
@@ -1,40 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import copy
|
||||
import openerp
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.osv import osv
|
||||
from lxml import etree, html
|
||||
from openerp import api
|
||||
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class view(osv.osv):
|
||||
class IrUiView(models.Model):
|
||||
_inherit = 'ir.ui.view'
|
||||
|
||||
@api.cr_uid_ids_context
|
||||
def render(self, cr, uid, id_or_xml_id, values=None, engine='ir.qweb', context=None):
|
||||
if isinstance(id_or_xml_id, list):
|
||||
id_or_xml_id = id_or_xml_id[0]
|
||||
if not values:
|
||||
values = {}
|
||||
if values.get('editable'):
|
||||
@api.multi
|
||||
def render(self, values=None, engine='ir.qweb'):
|
||||
if values and values.get('editable'):
|
||||
try:
|
||||
if not isinstance(id_or_xml_id, (int, long)):
|
||||
if '.' not in id_or_xml_id:
|
||||
raise ValueError('Invalid template id: %r' % (id_or_xml_id,))
|
||||
id_or_xml_id = self.get_view_id(cr, uid, id_or_xml_id, context=context)
|
||||
self.check_access_rights(cr, uid, 'write')
|
||||
self.check_access_rule(cr, uid, [id_or_xml_id], 'write', context=context)
|
||||
|
||||
self.check_access_rights('write')
|
||||
self.check_access_rule('write')
|
||||
except AccessError:
|
||||
values['editable'] = False
|
||||
|
||||
return super(view, self).render(cr, uid, id_or_xml_id, values=values, engine=engine, context=context)
|
||||
return super(IrUiView, self).render(values=values, engine=engine)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Save from html
|
||||
#------------------------------------------------------
|
||||
|
||||
def extract_embedded_fields(self, cr, uid, arch, context=None):
|
||||
@api.model
|
||||
def extract_embedded_fields(self, arch):
|
||||
return arch.xpath('//*[@data-oe-model != "ir.ui.view"]')
|
||||
|
||||
@api.model
|
||||
@@ -62,12 +55,12 @@ class view(osv.osv):
|
||||
return etree.tostring(
|
||||
arch_no_whitespace, encoding='unicode', pretty_print=True)
|
||||
|
||||
def replace_arch_section(self, cr, uid, view_id, section_xpath, replacement, context=None):
|
||||
@api.multi
|
||||
def replace_arch_section(self, section_xpath, replacement):
|
||||
# the root of the arch section shouldn't actually be replaced as it's
|
||||
# not really editable itself, only the content truly is editable.
|
||||
|
||||
[view] = self.browse(cr, uid, [view_id], context=context)
|
||||
arch = etree.fromstring(view.arch.encode('utf-8'))
|
||||
self.ensure_one()
|
||||
arch = etree.fromstring(self.arch.encode('utf-8'))
|
||||
# => get the replacement root
|
||||
if not section_xpath:
|
||||
root = arch
|
||||
@@ -84,7 +77,8 @@ class view(osv.osv):
|
||||
|
||||
return arch
|
||||
|
||||
def to_field_ref(self, cr, uid, el, context=None):
|
||||
@api.model
|
||||
def to_field_ref(self, el):
|
||||
# filter out meta-information inserted in the document
|
||||
attributes = dict((k, v) for k, v in el.items()
|
||||
if not k.startswith('data-oe-'))
|
||||
@@ -94,34 +88,28 @@ class view(osv.osv):
|
||||
out.tail = el.tail
|
||||
return out
|
||||
|
||||
def save(self, cr, uid, res_id, value, xpath=None, context=None):
|
||||
@api.multi
|
||||
def save(self, value, xpath=None):
|
||||
""" Update a view section. The view section may embed fields to write
|
||||
|
||||
:param str model:
|
||||
:param int res_id:
|
||||
:param str xpath: valid xpath to the tag to replace
|
||||
"""
|
||||
res_id = int(res_id)
|
||||
|
||||
arch_section = html.fromstring(
|
||||
value, parser=html.HTMLParser(encoding='utf-8'))
|
||||
|
||||
if xpath is None:
|
||||
# value is an embedded field on its own, not a view section
|
||||
self.save_embedded_field(cr, uid, arch_section, context=context)
|
||||
self.save_embedded_field(arch_section)
|
||||
return
|
||||
|
||||
for el in self.extract_embedded_fields(cr, uid, arch_section, context=context):
|
||||
self.save_embedded_field(cr, uid, el, context=context)
|
||||
for el in self.extract_embedded_fields(arch_section):
|
||||
self.save_embedded_field(el)
|
||||
|
||||
# transform embedded field back to t-field
|
||||
el.getparent().replace(el, self.to_field_ref(cr, uid, el, context=context))
|
||||
el.getparent().replace(el, self.to_field_ref(el))
|
||||
|
||||
arch = self.replace_arch_section(cr, uid, res_id, xpath, arch_section, context=context)
|
||||
self.write(cr, uid, res_id, {
|
||||
'arch': self._pretty_arch(arch)
|
||||
}, context=context)
|
||||
for view in self:
|
||||
arch = view.replace_arch_section(xpath, arch_section)
|
||||
view.write({'arch': view._pretty_arch(arch)})
|
||||
|
||||
view = self.browse(cr, openerp.SUPERUSER_ID, res_id, context=context)
|
||||
if view.model_data_id:
|
||||
view.model_data_id.write({'noupdate': True})
|
||||
self.sudo().mapped('model_data_id').write({'noupdate': True})
|
||||
|
||||
@@ -1,43 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp.osv import orm, fields
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class test_converter(orm.Model):
|
||||
class ConverterTest(models.Model):
|
||||
_name = 'web_editor.converter.test'
|
||||
|
||||
# disable translation export for those brilliant field labels and values
|
||||
_translate = False
|
||||
|
||||
_columns = {
|
||||
'char': fields.char(),
|
||||
'integer': fields.integer(),
|
||||
'float': fields.float(),
|
||||
'numeric': fields.float(digits=(16, 2)),
|
||||
'many2one': fields.many2one('web_editor.converter.test.sub'),
|
||||
'binary': fields.binary(),
|
||||
'date': fields.date(),
|
||||
'datetime': fields.datetime(),
|
||||
'selection': fields.selection([
|
||||
(1, "réponse A"),
|
||||
(2, "réponse B"),
|
||||
(3, "réponse C"),
|
||||
(4, "réponse <D>"),
|
||||
]),
|
||||
'selection_str': fields.selection([
|
||||
('A', "Qu'il n'est pas arrivé à Toronto"),
|
||||
('B', "Qu'il était supposé arriver à Toronto"),
|
||||
('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"),
|
||||
('D', "La réponse D"),
|
||||
], string=u"Lorsqu'un pancake prend l'avion à destination de Toronto et "
|
||||
u"qu'il fait une escale technique à St Claude, on dit:"),
|
||||
'html': fields.html(),
|
||||
'text': fields.text(),
|
||||
}
|
||||
char = fields.Char()
|
||||
integer = fields.Integer()
|
||||
float = fields.Float()
|
||||
numeric = fields.Float(digits=(16, 2))
|
||||
many2one = fields.Many2one('web_editor.converter.test.sub')
|
||||
binary = fields.Binary()
|
||||
date = fields.Date()
|
||||
datetime = fields.Datetime()
|
||||
selection = fields.Selection([
|
||||
(1, "réponse A"),
|
||||
(2, "réponse B"),
|
||||
(3, "réponse C"),
|
||||
(4, "réponse <D>"),
|
||||
])
|
||||
selection_str = fields.Selection([
|
||||
('A', "Qu'il n'est pas arrivé à Toronto"),
|
||||
('B', "Qu'il était supposé arriver à Toronto"),
|
||||
('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"),
|
||||
('D', "La réponse D"),
|
||||
], string=u"Lorsqu'un pancake prend l'avion à destination de Toronto et "
|
||||
u"qu'il fait une escale technique à St Claude, on dit:")
|
||||
html = fields.Html()
|
||||
text = fields.Text()
|
||||
|
||||
|
||||
class test_converter_sub(orm.Model):
|
||||
class ConverterTestSub(models.Model):
|
||||
_name = 'web_editor.converter.test.sub'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char(),
|
||||
}
|
||||
name = fields.Char()
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<span class="text-muted">— or — </span>
|
||||
<label for="iamgeurl">Add an image URL</label>
|
||||
<div class="form-group btn-group">
|
||||
<input class="form-control url pull-left" id="iamgeurl" name="url" placeholder="http://openerp.com/logo.png" style="width: 320px;" type="text"/>
|
||||
<input class="form-control url pull-left" id="iamgeurl" name="url" placeholder="https://www.odoo.com/logo.png" style="width: 320px;" type="text"/>
|
||||
<button class="btn btn-default" type="submit">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -238,7 +238,7 @@
|
||||
<label class="control-label" for="link-external">URL or Email Address
|
||||
</label>
|
||||
</h4>
|
||||
<input class="form-control url email-address url-source" id="link-external" placeholder="http://openerp.com" type="text"/>
|
||||
<input class="form-control url email-address url-source" id="link-external" placeholder="https://www.odoo.com" type="text"/>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="form-horizontal link-style">
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import test_ui
|
||||
import test_converter
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import textwrap
|
||||
import unittest
|
||||
|
||||
from lxml import etree, html
|
||||
from lxml.builder import E
|
||||
|
||||
from openerp.tests import common
|
||||
from openerp.addons.web_editor.models.ir_qweb import html_to_text
|
||||
from odoo.tests import common
|
||||
from odoo.addons.web_editor.models.ir_qweb import html_to_text
|
||||
|
||||
|
||||
class TestHTMLToText(unittest.TestCase):
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import openerp.tests
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
|
||||
|
||||
class TestUi(openerp.tests.HttpCase):
|
||||
class TestUi(odoo.tests.HttpCase):
|
||||
|
||||
post_install = True
|
||||
at_install = False
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
|
||||
<template id="layout" name="Editor layout"><!DOCTYPE html>
|
||||
<html t-att-lang="lang and lang.replace('_', '-')"
|
||||
@@ -66,4 +66,4 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -7,8 +7,11 @@ from lxml.html import builder as h
|
||||
|
||||
from openerp.tests import common
|
||||
|
||||
|
||||
def attrs(**kwargs):
|
||||
return dict(('data-oe-%s' % key, str(value)) for key, value in kwargs.iteritems())
|
||||
|
||||
|
||||
class TestViewSaving(common.TransactionCase):
|
||||
def eq(self, a, b):
|
||||
self.assertEqual(a.tag, b.tag)
|
||||
@@ -42,8 +45,7 @@ class TestViewSaving(common.TransactionCase):
|
||||
})
|
||||
|
||||
def test_embedded_extraction(self):
|
||||
fields = self.registry('ir.ui.view').extract_embedded_fields(
|
||||
self.cr, self.uid, self.arch, context=None)
|
||||
fields = self.env['ir.ui.view'].extract_embedded_fields(self.arch)
|
||||
|
||||
expect = [
|
||||
h.SPAN("My Company", attrs(model='res.company', id=1, field='name', type='char')),
|
||||
@@ -56,7 +58,7 @@ class TestViewSaving(common.TransactionCase):
|
||||
embedded = h.SPAN("+00 00 000 00 0 000", attrs(
|
||||
model='res.company', id=1, field='phone', type='char'))
|
||||
|
||||
self.registry('ir.ui.view').save_embedded_field(self.cr, self.uid, embedded)
|
||||
self.env['ir.ui.view'].save_embedded_field(embedded)
|
||||
|
||||
company = self.registry('res.company').browse(self.cr, self.uid, 1)
|
||||
self.assertEqual(company.phone, "+00 00 000 00 0 000")
|
||||
@@ -66,37 +68,37 @@ class TestViewSaving(common.TransactionCase):
|
||||
e1 = h.SPAN("My Company", attrs(model='res.company', id=1, field='name'))
|
||||
e2 = h.SPAN("Leeroy Jenkins", attrs(model='res.company', id=1, field='name'))
|
||||
|
||||
View = self.registry('ir.ui.view')
|
||||
View = self.env['ir.ui.view']
|
||||
|
||||
View.save_embedded_field(self.cr, self.uid, e1)
|
||||
View.save_embedded_field(e1)
|
||||
# FIXME: more precise exception
|
||||
with self.assertRaises(Exception):
|
||||
View.save_embedded_field(self.cr, self.uid, e2)
|
||||
View.save_embedded_field(e2)
|
||||
|
||||
def test_embedded_to_field_ref(self):
|
||||
View = self.registry('ir.ui.view')
|
||||
View = self.env['ir.ui.view']
|
||||
embedded = h.SPAN("My Company", attrs(expression="bob"))
|
||||
self.eq(
|
||||
View.to_field_ref(self.cr, self.uid, embedded, context=None),
|
||||
View.to_field_ref(embedded),
|
||||
h.SPAN({'t-field': 'bob'})
|
||||
)
|
||||
|
||||
def test_to_field_ref_keep_attributes(self):
|
||||
View = self.registry('ir.ui.view')
|
||||
View = self.env['ir.ui.view']
|
||||
|
||||
att = attrs(expression="bob", model="res.company", id=1, field="name")
|
||||
att['id'] = "whop"
|
||||
att['class'] = "foo bar"
|
||||
embedded = h.SPAN("My Company", att)
|
||||
|
||||
self.eq(View.to_field_ref(self.cr, self.uid, embedded, context=None),
|
||||
self.eq(View.to_field_ref(embedded),
|
||||
h.SPAN({'t-field': 'bob', 'class': 'foo bar', 'id': 'whop'}))
|
||||
|
||||
def test_replace_arch(self):
|
||||
replacement = h.P("Wheee")
|
||||
|
||||
result = self.registry('ir.ui.view').replace_arch_section(
|
||||
self.cr, self.uid, self.view_id, None, replacement)
|
||||
self.cr, self.uid, [self.view_id], None, replacement)
|
||||
|
||||
self.eq(result, h.DIV("Wheee"))
|
||||
|
||||
@@ -104,7 +106,7 @@ class TestViewSaving(common.TransactionCase):
|
||||
replacement = h.DIV(h.P("Wheee"))
|
||||
|
||||
result = self.registry('ir.ui.view').replace_arch_section(
|
||||
self.cr, self.uid, self.view_id, None, replacement)
|
||||
self.cr, self.uid, [self.view_id], None, replacement)
|
||||
|
||||
self.eq(result, replacement)
|
||||
|
||||
@@ -112,7 +114,7 @@ class TestViewSaving(common.TransactionCase):
|
||||
replacement = h.H1("I am the greatest title alive!")
|
||||
|
||||
result = self.registry('ir.ui.view').replace_arch_section(
|
||||
self.cr, self.uid, self.view_id, '/div/div[1]/h3',
|
||||
self.cr, self.uid, [self.view_id], '/div/div[1]/h3',
|
||||
replacement)
|
||||
|
||||
self.eq(result, h.DIV(
|
||||
@@ -134,12 +136,12 @@ class TestViewSaving(common.TransactionCase):
|
||||
def test_multiple_xpath_matches(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.registry('ir.ui.view').replace_arch_section(
|
||||
self.cr, self.uid, self.view_id, '/div/div/h3',
|
||||
self.cr, self.uid, [self.view_id], '/div/div/h3',
|
||||
h.H6("Lol nope"))
|
||||
|
||||
def test_save(self):
|
||||
Company = self.registry('res.company')
|
||||
View = self.registry('ir.ui.view')
|
||||
View = self.env['ir.ui.view']
|
||||
|
||||
replacement = ET.tostring(h.DIV(
|
||||
h.H3("Column 2"),
|
||||
@@ -149,14 +151,13 @@ class TestViewSaving(common.TransactionCase):
|
||||
h.LI(h.SPAN("+12 3456789", attrs(model='res.company', id=1, field='phone', expression="edmund", type='char'))),
|
||||
)
|
||||
), encoding='utf-8')
|
||||
View.save(self.cr, self.uid, res_id=self.view_id, value=replacement,
|
||||
xpath='/div/div[2]')
|
||||
View.browse(self.view_id).save(value=replacement, xpath='/div/div[2]')
|
||||
|
||||
company = Company.browse(self.cr, self.uid, 1)
|
||||
self.assertEqual(company.name, "Acme Corporation")
|
||||
self.assertEqual(company.phone, "+12 3456789")
|
||||
self.eq(
|
||||
ET.fromstring(View.browse(self.cr, self.uid, self.view_id).arch.encode('utf-8')),
|
||||
ET.fromstring(View.browse(self.view_id).arch.encode('utf-8')),
|
||||
h.DIV(
|
||||
h.DIV(
|
||||
h.H3("Column 1"),
|
||||
@@ -180,7 +181,7 @@ class TestViewSaving(common.TransactionCase):
|
||||
'arch':'<t><p><h1>hello world</h1></p></t>',
|
||||
'type':'qweb'
|
||||
})
|
||||
view = self.registry('ir.ui.view').browse(self.cr, self.uid, view_id)
|
||||
view = self.env['ir.ui.view'].browse(view_id)
|
||||
# script and style text nodes should not escaped client side
|
||||
replacement = '<script>1 && "hello & world"</script>'
|
||||
view.save(replacement, xpath='/t/p/h1')
|
||||
@@ -212,26 +213,25 @@ class TestViewSaving(common.TransactionCase):
|
||||
node = html.tostring(h.SPAN(
|
||||
"Acme Corporation",
|
||||
attrs(model='res.company', id=company_id, field="name", expression='bob', type='char')))
|
||||
|
||||
self.registry('ir.ui.view').save(self.cr, self.uid, res_id=company_id,value=node)
|
||||
View = self.env['ir.ui.view']
|
||||
View.browse(company_id).save(value=node)
|
||||
|
||||
company = Company.browse(self.cr, self.uid, company_id)
|
||||
self.assertEqual(company.name, "Acme Corporation")
|
||||
|
||||
|
||||
def test_field_tail(self):
|
||||
View = self.registry('ir.ui.view')
|
||||
View = self.env['ir.ui.view']
|
||||
replacement = ET.tostring(
|
||||
h.LI(h.SPAN("+12 3456789", attrs(
|
||||
model='res.company', id=1, type='char',
|
||||
field='phone', expression="edmund")),
|
||||
"whop whop"
|
||||
), encoding="utf-8")
|
||||
View.save(self.cr, self.uid, res_id = self.view_id, value=replacement,
|
||||
xpath='/div/div[2]/ul/li[3]')
|
||||
View.browse(self.view_id).save(value=replacement, xpath='/div/div[2]/ul/li[3]')
|
||||
|
||||
self.eq(
|
||||
ET.fromstring(View.browse(self.cr, self.uid, self.view_id).arch.encode('utf-8')),
|
||||
ET.fromstring(View.browse(self.view_id).arch.encode('utf-8')),
|
||||
h.DIV(
|
||||
h.DIV(
|
||||
h.H3("Column 1"),
|
||||
|
||||
Reference in New Issue
Block a user