[REF] fields: replace old-API _symbol_get/_symbol_set by field converters
This commit is contained in:
+88
-3
@@ -8,13 +8,16 @@ from datetime import date, datetime
|
||||
from functools import partial
|
||||
from operator import attrgetter
|
||||
from types import NoneType
|
||||
import json
|
||||
import logging
|
||||
import pytz
|
||||
import xmlrpclib
|
||||
|
||||
import psycopg2
|
||||
|
||||
from openerp.sql_db import LazyCursor
|
||||
from openerp.tools import float_precision, float_round, frozendict, \
|
||||
html_sanitize, pg_varchar, ustr, OrderedSet
|
||||
from openerp.tools import float_precision, float_repr, float_round, frozendict, \
|
||||
html_sanitize, human_size, pg_varchar, ustr, OrderedSet
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
|
||||
from openerp.tools.translate import html_translate
|
||||
@@ -291,6 +294,7 @@ class Field(object):
|
||||
relational = False # whether the field is a relational one
|
||||
|
||||
column_type = None # database column type (ident, spec)
|
||||
column_format = '%s' # placeholder for value in queries
|
||||
|
||||
_slots = {
|
||||
'args': EMPTY_DICT, # the parameters given to __init__()
|
||||
@@ -797,6 +801,14 @@ class Field(object):
|
||||
""" Return the null value for this field in the record format. """
|
||||
return False
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
""" Convert ``value`` from the ``write`` format to the SQL format. """
|
||||
if value is None or value == False:
|
||||
return None
|
||||
if isinstance(value, unicode):
|
||||
return value.encode('utf8')
|
||||
return str(value)
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
""" Convert ``value`` to the cache format; ``value`` may come from an
|
||||
assignment, or have the format of methods :meth:`BaseModel.read` or
|
||||
@@ -1090,6 +1102,9 @@ class Boolean(Field):
|
||||
type = 'boolean'
|
||||
column_type = ('bool', 'bool')
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
return bool(value)
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
return bool(value)
|
||||
|
||||
@@ -1106,6 +1121,9 @@ class Integer(Field):
|
||||
'group_operator': 'sum',
|
||||
}
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
return int(value or 0)
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
if isinstance(value, dict):
|
||||
# special case, when an integer field is used as inverse for a one2many
|
||||
@@ -1169,6 +1187,14 @@ class Float(Field):
|
||||
_column_digits = property(lambda self: not callable(self._digits) and self._digits)
|
||||
_column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
result = float(value or 0.0)
|
||||
digits = self.digits
|
||||
if digits:
|
||||
precision, scale = digits
|
||||
result = float_repr(float_round(result, precision_digits=scale), precision_digits=scale)
|
||||
return result
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
# apply rounding here, otherwise value in cache may be wrong!
|
||||
value = float(value or 0.0)
|
||||
@@ -1215,6 +1241,12 @@ class Monetary(Field):
|
||||
assert self.currency_field in model._fields, \
|
||||
"Field %s with unknown currency_field %r" % (self, self.currency_field)
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
try:
|
||||
return value.float_repr() # see float_precision.float_repr()
|
||||
except Exception:
|
||||
return float(value or 0.0)
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
if validate:
|
||||
currency = record[self.currency_field]
|
||||
@@ -1307,6 +1339,17 @@ class Char(_String):
|
||||
assert isinstance(self.size, (NoneType, int)), \
|
||||
"Char field %s with non-integer size %r" % (self, self.size)
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
#TODO:
|
||||
# * we need to remove the "value==False" from the next line BUT
|
||||
# for now too many things rely on this broken behavior
|
||||
# * the value==None test should be common to all data types
|
||||
if value is None or value == False:
|
||||
return None
|
||||
# we need to convert the string to a unicode object to be able
|
||||
# to evaluate its length (and possibly truncate it) reliably
|
||||
return ustr(value)[:self.size].encode('utf8')
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
if value is None or value is False:
|
||||
return False
|
||||
@@ -1374,6 +1417,19 @@ class Html(_String):
|
||||
_related_strip_classes = property(attrgetter('strip_classes'))
|
||||
_description_strip_classes = property(attrgetter('strip_classes'))
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
if value is None or value is False:
|
||||
return None
|
||||
if self.sanitize:
|
||||
return html_sanitize(
|
||||
value, silent=True,
|
||||
sanitize_tags=self.sanitize_tags,
|
||||
sanitize_attributes=self.sanitize_attributes,
|
||||
sanitize_style=self.sanitize_style,
|
||||
strip_style=self.strip_style,
|
||||
strip_classes=self.strip_classes)
|
||||
return value
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
if value is None or value is False:
|
||||
return False
|
||||
@@ -1540,6 +1596,27 @@ class Binary(Field):
|
||||
_column_attachment = property(attrgetter('attachment'))
|
||||
_description_attachment = property(attrgetter('attachment'))
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
# Binary values may be byte strings (python 2.6 byte array), but
|
||||
# the legacy OpenERP convention is to transfer and store binaries
|
||||
# as base64-encoded strings. The base64 string may be provided as a
|
||||
# unicode in some circumstances, hence the str() cast here.
|
||||
# This str() coercion will only work for pure ASCII unicode strings,
|
||||
# on purpose - non base64 data must be passed as a 8bit byte strings.
|
||||
return psycopg2.Binary(str(value)) if value else None
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
if isinstance(value, buffer):
|
||||
return str(value)
|
||||
if isinstance(value, (int, long)) and \
|
||||
(record._context.get('bin_size') or
|
||||
record._context.get('bin_size_' + self.name)):
|
||||
# If the client requests only the size of the field, we return that
|
||||
# instead of the content. Presumably a separate request will be done
|
||||
# to read the actual content, if necessary.
|
||||
return human_size(value)
|
||||
return value
|
||||
|
||||
|
||||
class Selection(Field):
|
||||
"""
|
||||
@@ -1786,6 +1863,9 @@ class Many2one(_Relational):
|
||||
"""
|
||||
records._cache[self] = self.convert_to_cache(value, records, validate=False)
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
return value or None
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
# cache format: tuple(ids)
|
||||
def process(ids):
|
||||
@@ -2122,8 +2202,13 @@ class Serialized(Field):
|
||||
type = 'serialized'
|
||||
column_type = ('text', 'text')
|
||||
|
||||
def convert_to_column(self, value, record):
|
||||
return json.dumps(value)
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
return value or {}
|
||||
# cache format: dict
|
||||
value = value or {}
|
||||
return value if isinstance(value, dict) else json.loads(value)
|
||||
|
||||
|
||||
class Id(Field):
|
||||
|
||||
+18
-27
@@ -2312,13 +2312,12 @@ class BaseModel(object):
|
||||
# get the default value; ideally, we should use default_get(), but it
|
||||
# fails due to ir.values not being ready
|
||||
field = self._fields[column_name]
|
||||
setc, setf = field.column._symbol_set
|
||||
if field.default:
|
||||
value = field.default(self)
|
||||
value = field.convert_to_cache(value, self, validate=False)
|
||||
value = field.convert_to_record(value, self)
|
||||
value = field.convert_to_write(value, self)
|
||||
value = setf(value)
|
||||
value = field.convert_to_column(value, self)
|
||||
else:
|
||||
value = None
|
||||
# Write value if non-NULL, except for booleans for which False means
|
||||
@@ -2328,7 +2327,7 @@ class BaseModel(object):
|
||||
_logger.debug("Table '%s': setting default value of new column %s to %r",
|
||||
self._table, column_name, value)
|
||||
query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" IS NULL' % (
|
||||
self._table, column_name, setc, column_name)
|
||||
self._table, column_name, field.column_format, column_name)
|
||||
self._cr.execute(query, (value,))
|
||||
# this is a disgrace
|
||||
self._cr.commit()
|
||||
@@ -3230,14 +3229,6 @@ class BaseModel(object):
|
||||
for vals in result:
|
||||
vals[f] = translate(vals['id'], vals[f])
|
||||
|
||||
# apply the symbol_get functions of the fields we just read
|
||||
for field in fields_pre:
|
||||
symbol_get = field.base_field.column._symbol_get
|
||||
if symbol_get:
|
||||
f = field.name
|
||||
for vals in result:
|
||||
vals[f] = symbol_get(vals[f])
|
||||
|
||||
# store result in cache for POST fields
|
||||
for vals in result:
|
||||
record = self.browse(vals['id'], self._prefetch)
|
||||
@@ -3710,15 +3701,14 @@ class BaseModel(object):
|
||||
field = self._fields[name]
|
||||
if field and field.deprecated:
|
||||
_logger.warning('Field %s.%s is deprecated: %s', self._name, name, field.deprecated)
|
||||
if field.column:
|
||||
column = field.column
|
||||
if hasattr(column, 'selection') and val:
|
||||
if field.store:
|
||||
if hasattr(field, 'selection') and val:
|
||||
self._check_selection_field_value(name, val)
|
||||
if column._classic_write and not hasattr(column, '_fnct_inv'):
|
||||
if single_lang or not (has_trans and column.translate and not callable(column.translate)):
|
||||
if field.column_type:
|
||||
if single_lang or not (has_trans and getattr(field, 'translate', None) and not callable(field.translate)):
|
||||
# val is not a translation: update the table
|
||||
setc, setf = column._symbol_set
|
||||
updates.append((name, setc, setf(val)))
|
||||
val = field.convert_to_column(val, self)
|
||||
updates.append((name, field.column_format, val))
|
||||
direct.append(name)
|
||||
else:
|
||||
upd_todo.append(name)
|
||||
@@ -3744,20 +3734,22 @@ class BaseModel(object):
|
||||
|
||||
# TODO: optimize
|
||||
for name in direct:
|
||||
column = self._columns[name]
|
||||
if callable(column.translate):
|
||||
field = self._fields[name]
|
||||
translate = getattr(field, 'translate', None)
|
||||
|
||||
if callable(translate):
|
||||
# The source value of a field has been modified,
|
||||
# synchronize translated terms when possible.
|
||||
self.env['ir.translation']._sync_terms_translations(self._fields[name], self)
|
||||
|
||||
elif has_trans and column.translate:
|
||||
elif has_trans and translate:
|
||||
# The translated value of a field has been modified.
|
||||
src_trans = self.read([name])[0][name]
|
||||
if not src_trans:
|
||||
# Insert value to DB
|
||||
src_trans = vals[name]
|
||||
self.with_context(lang=None).write({name: src_trans})
|
||||
val = column._symbol_set[1](vals[name])
|
||||
val = field.convert_to_column(vals[name], self)
|
||||
tname = "%s,%s" % (self._name, name)
|
||||
self.env['ir.translation']._set_ids(
|
||||
tname, 'model', self.env.lang, self.ids, val, src_trans)
|
||||
@@ -3987,14 +3979,13 @@ class BaseModel(object):
|
||||
|
||||
# determine SQL values
|
||||
for name, val in vals.iteritems():
|
||||
column = self._columns[name]
|
||||
if column._classic_write:
|
||||
setc, setf = column._symbol_set
|
||||
updates.append((name, setc, setf(val)))
|
||||
field = self._fields[name]
|
||||
if field.store and field.column_type:
|
||||
updates.append((name, field.column_format, field.convert_to_column(val, self)))
|
||||
else:
|
||||
upd_todo.append(name)
|
||||
|
||||
if hasattr(column, 'selection') and val:
|
||||
if hasattr(field, 'selection') and val:
|
||||
self._check_selection_field_value(name, val)
|
||||
|
||||
if self._log_access:
|
||||
|
||||
@@ -117,6 +117,7 @@ import collections
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
from functools import partial
|
||||
from zlib import crc32
|
||||
|
||||
import openerp.modules
|
||||
@@ -1182,9 +1183,9 @@ class expression(object):
|
||||
if left == 'id':
|
||||
instr = ','.join(['%s'] * len(params))
|
||||
else:
|
||||
ss = model._columns[left]._symbol_set
|
||||
instr = ','.join([ss[0]] * len(params))
|
||||
params = map(ss[1], params)
|
||||
field = model._fields[left]
|
||||
instr = ','.join([field.column_format] * len(params))
|
||||
params = map(partial(field.convert_to_column, record=model), params)
|
||||
query = '(%s."%s" %s (%s))' % (table_alias, left, operator, instr)
|
||||
else:
|
||||
# The case for (left, 'in', []) or (left, 'not in', []).
|
||||
@@ -1235,7 +1236,7 @@ class expression(object):
|
||||
cast = '::text' if sql_operator.endswith('like') else ''
|
||||
|
||||
if left in model._columns:
|
||||
format = need_wildcard and '%s' or model._columns[left]._symbol_set[0]
|
||||
format = need_wildcard and '%s' or model._fields[left].column_format
|
||||
unaccent = self._unaccent if sql_operator.endswith('like') else lambda x: x
|
||||
column = '%s.%s' % (table_alias, _quote(left))
|
||||
query = '(%s %s %s)' % (unaccent(column + cast), sql_operator, unaccent(format))
|
||||
@@ -1256,7 +1257,7 @@ class expression(object):
|
||||
params = '%%%s%%' % str_utf8
|
||||
add_null = not str_utf8
|
||||
elif left in model._columns:
|
||||
params = model._columns[left]._symbol_set[1](right)
|
||||
params = model._fields[left].convert_to_column(right, model)
|
||||
|
||||
if add_null:
|
||||
query = '(%s OR %s."%s" IS NULL)' % (query, table_alias, left)
|
||||
|
||||
Reference in New Issue
Block a user