[IMP] move conversion out of view.save
Also, html.fromstring does weird stuff when encoding is not specified, force to utf-8 just in case. bzr revid: xmo@openerp.com-20130930145358-qh7xdicgg21prsk4
This commit is contained in:
parent
3b6f61b2ac
commit
ce30e7691e
|
@ -1,5 +1,3 @@
|
||||||
import controllers
|
import controllers
|
||||||
import res_config
|
|
||||||
|
|
||||||
import website
|
import models
|
||||||
import view
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import ir_fields
|
||||||
|
import res_config
|
||||||
|
import view
|
||||||
|
import website
|
|
@ -0,0 +1,87 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from lxml import etree, html
|
||||||
|
|
||||||
|
from openerp.osv import orm, fields
|
||||||
|
from openerp.tools import ustr
|
||||||
|
|
||||||
|
class converter(orm.Model):
|
||||||
|
_inherit = 'ir.fields.converter'
|
||||||
|
|
||||||
|
def _html_to_integer(self, cr, uid, model, column, value, context=None):
|
||||||
|
return int(value.text_content().strip()), []
|
||||||
|
|
||||||
|
def _html_to_float(self, cr, uid, model, column, value, context=None):
|
||||||
|
return float(value.text_content().strip()), []
|
||||||
|
|
||||||
|
def _html_to_passthrough(self, cr, uid, model, column, value, context=None):
|
||||||
|
return value.text_content().strip(), []
|
||||||
|
|
||||||
|
_html_to_char = _html_to_date = _html_to_datetime = _html_to_passthrough
|
||||||
|
|
||||||
|
def _html_to_text(self, cr, uid, model, column, value, context=None):
|
||||||
|
return value.text_content(), []
|
||||||
|
|
||||||
|
def _html_to_selection(self, cr, uid, model, column, value, context=None):
|
||||||
|
text = value.text_content().strip()
|
||||||
|
|
||||||
|
selection = column.reify(cr, uid, model, column, context=context)
|
||||||
|
for k, v in selection:
|
||||||
|
if isinstance(v, str):
|
||||||
|
v = ustr(v)
|
||||||
|
if text == v:
|
||||||
|
return k, []
|
||||||
|
|
||||||
|
warning = u"No value found for label %s in selection %s" % (text, selection)
|
||||||
|
# FIXME: ?
|
||||||
|
return False, [Warning(warning.encode('utf-8'))]
|
||||||
|
|
||||||
|
def _html_to_many2one(self, cr, uid, model, column, value, context=None):
|
||||||
|
matches = self.pool[column._obj].name_search(
|
||||||
|
cr, uid, name=value.text_content().strip(), context=context)
|
||||||
|
# FIXME: more than one match, error reporting
|
||||||
|
return matches[0][0], []
|
||||||
|
|
||||||
|
def _html_to_html(self, cr, uid, model, column, value, context=None):
|
||||||
|
content = []
|
||||||
|
if value.text: content.append(value.text)
|
||||||
|
content.extend(html.tostring(child)
|
||||||
|
for child in value.iterchildren(tag=etree.Element))
|
||||||
|
return '\n'.join(content), []
|
||||||
|
|
||||||
|
|
||||||
|
class test_converter(orm.Model):
|
||||||
|
_name = 'website.converter.test'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'char': fields.char(),
|
||||||
|
'integer': fields.integer(),
|
||||||
|
'float': fields.float(),
|
||||||
|
'numeric': fields.float(digits=(16, 2)),
|
||||||
|
'many2one': fields.many2one('website.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="Lorsqu'un pancake prend l'avion à destination de Toronto et "
|
||||||
|
"qu'il fait une escale technique à St Claude, on dit:"),
|
||||||
|
'html': fields.html(),
|
||||||
|
'text': fields.text(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class test_converter_sub(orm.Model):
|
||||||
|
_name = 'website.converter.test.sub'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'name': fields.char(),
|
||||||
|
}
|
|
@ -55,57 +55,19 @@ class view(osv.osv):
|
||||||
def extract_embedded_fields(self, cr, uid, arch, context=None):
|
def extract_embedded_fields(self, cr, uid, arch, context=None):
|
||||||
return arch.xpath('//*[@data-oe-model != "ir.ui.view"]')
|
return arch.xpath('//*[@data-oe-model != "ir.ui.view"]')
|
||||||
|
|
||||||
def convert_embedded_field(self, cr, uid, el, column,
|
|
||||||
type_override=None, context=None):
|
|
||||||
""" Converts the content of an embedded field to a value acceptable
|
|
||||||
for writing in the column
|
|
||||||
|
|
||||||
:param etree._Element el: embedded field being saved
|
|
||||||
:param fields._column column: column object corresponding to the field
|
|
||||||
:param type type_override: column type to dispatch on instead of the
|
|
||||||
column's actual type (for proxy column types
|
|
||||||
e.g. relateds)
|
|
||||||
:return: converted value
|
|
||||||
"""
|
|
||||||
column_type = type_override or type(column)
|
|
||||||
|
|
||||||
if issubclass(column_type, fields.html):
|
|
||||||
content = []
|
|
||||||
if el.text: content.append(el.text)
|
|
||||||
content.extend(html.tostring(child)
|
|
||||||
for child in el.iterchildren(tag=etree.Element))
|
|
||||||
return '\n'.join(content)
|
|
||||||
elif issubclass(column_type, fields.integer):
|
|
||||||
return int(el.text_content())
|
|
||||||
elif issubclass(column_type, fields.float):
|
|
||||||
return float(el.text_content())
|
|
||||||
elif issubclass(column_type, (fields.char, fields.text,
|
|
||||||
fields.date, fields.datetime)):
|
|
||||||
return el.text_content()
|
|
||||||
# TODO: fields.selection
|
|
||||||
elif issubclass(column_type, fields.many2one):
|
|
||||||
matches = self.pool[column._obj].name_search(
|
|
||||||
cr, uid, name=el.text_content().strip(), context=context)
|
|
||||||
# FIXME: more than one match, error reporting
|
|
||||||
return matches[0][0]
|
|
||||||
elif issubclass(column_type, fields.function):
|
|
||||||
# FIXME: special-case selection as in get_pg_type?
|
|
||||||
return self.convert_embedded_field(
|
|
||||||
cr, uid, el, column,
|
|
||||||
type_override=getattr(fields, column._type),
|
|
||||||
context=context)
|
|
||||||
# TODO?: fields.many2many, fields.one2many
|
|
||||||
# TODO?: fields.reference
|
|
||||||
else:
|
|
||||||
raise TypeError("Un-convertable column type %s" % column_type)
|
|
||||||
|
|
||||||
def save_embedded_field(self, cr, uid, el, context=None):
|
def save_embedded_field(self, cr, uid, el, context=None):
|
||||||
Model = self.pool[el.get('data-oe-model')]
|
Model = self.pool[el.get('data-oe-model')]
|
||||||
field = el.get('data-oe-field')
|
field = el.get('data-oe-field')
|
||||||
|
|
||||||
column = Model._all_columns[field].column
|
column = Model._all_columns[field].column
|
||||||
|
convert = self.pool['ir.fields.converter'].to_field(
|
||||||
|
cr, uid, Model, column, fromtype="html", context=context)
|
||||||
|
value, warnings = convert(el)
|
||||||
|
# FIXME: report error
|
||||||
|
if warnings: return
|
||||||
|
|
||||||
Model.write(cr, uid, [int(el.get('data-oe-id'))], {
|
Model.write(cr, uid, [int(el.get('data-oe-id'))], {
|
||||||
field: self.convert_embedded_field(cr, uid, el, column, context=context)
|
field: value
|
||||||
}, context=context)
|
}, context=context)
|
||||||
|
|
||||||
def to_field_ref(self, cr, uid, el, context=None):
|
def to_field_ref(self, cr, uid, el, context=None):
|
||||||
|
@ -137,7 +99,8 @@ class view(osv.osv):
|
||||||
"""
|
"""
|
||||||
res_id = int(res_id)
|
res_id = int(res_id)
|
||||||
|
|
||||||
arch_section = html.fromstring(value)
|
arch_section = html.fromstring(
|
||||||
|
value, parser=html.HTMLParser(encoding='utf-8'))
|
||||||
|
|
||||||
if xpath is None:
|
if xpath is None:
|
||||||
# value is an embedded field on its own, not a view section
|
# value is an embedded field on its own, not a view section
|
|
@ -1,4 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import test_views
|
import test_views, test_converter
|
||||||
|
|
||||||
checks = [ test_views ]
|
checks = [ test_views, test_converter, ]
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from collections import namedtuple
|
||||||
|
from functools import partial
|
||||||
|
from xml.dom.minidom import getDOMImplementation
|
||||||
|
|
||||||
|
from lxml import etree, html
|
||||||
|
|
||||||
|
from openerp.tests import common
|
||||||
|
from openerp.tools import qweb
|
||||||
|
import unittest2
|
||||||
|
|
||||||
|
renderer = qweb.QWebXml()
|
||||||
|
impl = getDOMImplementation()
|
||||||
|
document = impl.createDocument(None, None, None)
|
||||||
|
|
||||||
|
Request = namedtuple('Request', 'cr uid registry')
|
||||||
|
class RegistryProxy(object):
|
||||||
|
def __init__(self, func):
|
||||||
|
self.func = func
|
||||||
|
def __getitem__(self, name):
|
||||||
|
return self.func(name)
|
||||||
|
|
||||||
|
class TestConvertBack(common.TransactionCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestConvertBack, self).setUp()
|
||||||
|
|
||||||
|
self.Converter = self.registry('ir.fields.converter')
|
||||||
|
|
||||||
|
def field_rountrip_result(self, field, value, expected):
|
||||||
|
model = 'website.converter.test'
|
||||||
|
Model = self.registry(model)
|
||||||
|
id = Model.create(
|
||||||
|
self.cr, self.uid, {
|
||||||
|
field: value
|
||||||
|
})
|
||||||
|
[record] = Model.browse(self.cr, self.uid, [id])
|
||||||
|
|
||||||
|
e = document.createElement('span')
|
||||||
|
field_value = 'record.%s' % field
|
||||||
|
e.setAttribute('t-field', field_value)
|
||||||
|
|
||||||
|
rendered = renderer.render_tag_field(
|
||||||
|
e, {'field': field_value}, '', {
|
||||||
|
'record': record,
|
||||||
|
'request': Request(self.cr, self.uid, RegistryProxy( self.registry)),
|
||||||
|
})
|
||||||
|
element = html.fromstring(
|
||||||
|
rendered, parser=html.HTMLParser(encoding='utf-8'))
|
||||||
|
|
||||||
|
column = Model._all_columns[field].column
|
||||||
|
|
||||||
|
from_html = self.Converter.to_field(
|
||||||
|
self.cr, self.uid, model, column, 'html')
|
||||||
|
|
||||||
|
value_back, warnings = from_html(element)
|
||||||
|
self.assertEqual(warnings, [])
|
||||||
|
|
||||||
|
if isinstance(expected, str):
|
||||||
|
expected = expected.decode('utf-8')
|
||||||
|
self.assertEqual(value_back, expected)
|
||||||
|
|
||||||
|
def field_roundtrip(self, field, value):
|
||||||
|
self.field_rountrip_result(field, value, value)
|
||||||
|
|
||||||
|
def test_integer(self):
|
||||||
|
self.field_roundtrip('integer', 42)
|
||||||
|
|
||||||
|
def test_float(self):
|
||||||
|
self.field_roundtrip('float', 42.567890)
|
||||||
|
|
||||||
|
def test_numeric(self):
|
||||||
|
self.field_roundtrip('numeric', 42.77)
|
||||||
|
|
||||||
|
def test_char(self):
|
||||||
|
self.field_roundtrip('char', "foo bar")
|
||||||
|
|
||||||
|
self.field_roundtrip('char', "ⒸⓄⓇⒼⒺ")
|
||||||
|
|
||||||
|
def test_m2o(self):
|
||||||
|
Sub = self.registry('website.converter.test.sub')
|
||||||
|
sub = partial(Sub.create, self.cr, self.uid)
|
||||||
|
ids = [
|
||||||
|
sub({'name': "Foo"}),
|
||||||
|
sub({'name': "Bar"}),
|
||||||
|
sub({'name': "Baz"}),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.field_rountrip_result(
|
||||||
|
'many2one',
|
||||||
|
ids[2],
|
||||||
|
ids[2])
|
||||||
|
|
||||||
|
def test_selection(self):
|
||||||
|
self.field_roundtrip('selection', 3)
|
||||||
|
|
||||||
|
def test_selection_str(self):
|
||||||
|
self.field_roundtrip('selection_str', 'B')
|
||||||
|
|
||||||
|
def test_text(self):
|
||||||
|
self.field_roundtrip('text', """
|
||||||
|
You must obey the dance commander
|
||||||
|
Givin' out the order for fun
|
||||||
|
You must obey the dance commander
|
||||||
|
You know that he's the only one
|
||||||
|
Who gives the orders here,
|
||||||
|
Alright
|
||||||
|
Who gives the orders here,
|
||||||
|
Alright
|
||||||
|
|
||||||
|
It would be awesome
|
||||||
|
If we could dance-a
|
||||||
|
It would be awesome, yeah
|
||||||
|
Let's take the chance-a
|
||||||
|
It would be awesome, yeah
|
||||||
|
Let's start the show
|
||||||
|
Because you never know
|
||||||
|
You never know
|
||||||
|
You never know until you go
|
||||||
|
""")
|
Loading…
Reference in New Issue