[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 res_config
|
||||
|
||||
import website
|
||||
import view
|
||||
import models
|
||||
|
|
|
@ -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):
|
||||
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):
|
||||
Model = self.pool[el.get('data-oe-model')]
|
||||
field = el.get('data-oe-field')
|
||||
|
||||
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'))], {
|
||||
field: self.convert_embedded_field(cr, uid, el, column, context=context)
|
||||
field: value
|
||||
}, context=context)
|
||||
|
||||
def to_field_ref(self, cr, uid, el, context=None):
|
||||
|
@ -137,7 +99,8 @@ class view(osv.osv):
|
|||
"""
|
||||
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:
|
||||
# value is an embedded field on its own, not a view section
|
|
@ -1,4 +1,4 @@
|
|||
# -*- 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