2009-10-20 10:52:23 +00:00
# -*- coding: utf-8 -*-
2006-12-07 13:41:40 +00:00
##############################################################################
2010-03-08 09:40:36 +00:00
#
2008-11-25 07:07:40 +00:00
# OpenERP, Open Source Management Solution
2009-11-27 07:23:48 +00:00
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
2008-11-03 18:27:16 +00:00
#
# This program is free software: you can redistribute it and/or modify
2009-11-27 07:23:48 +00:00
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
2008-11-03 18:27:16 +00:00
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2009-11-27 07:23:48 +00:00
# GNU Affero General Public License for more details.
2008-11-03 18:27:16 +00:00
#
2009-11-27 07:23:48 +00:00
# You should have received a copy of the GNU Affero General Public License
2010-03-08 09:40:36 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2006-12-07 13:41:40 +00:00
#
##############################################################################
2011-06-23 09:02:28 +00:00
""" Fields:
- simple
- relations ( one2many , many2one , many2many )
- function
Fields Attributes :
* _classic_read : is a classic sql fields
* _type : field type
* readonly
* required
* size
"""
2011-09-06 17:31:40 +00:00
import base64
2010-10-12 16:01:30 +00:00
import datetime as DT
2012-01-25 13:24:07 +00:00
import logging
2012-02-13 18:03:55 +00:00
import pytz
2011-09-06 17:31:40 +00:00
import re
2010-12-09 23:07:42 +00:00
import xmlrpclib
2008-12-08 14:03:42 +00:00
from psycopg2 import Binary
2006-12-07 13:41:40 +00:00
2011-10-05 15:48:57 +00:00
import openerp
2011-02-07 12:57:23 +00:00
import openerp . tools as tools
from openerp . tools . translate import _
2011-12-22 18:24:05 +00:00
from openerp . tools import float_round , float_repr
2012-01-18 12:33:34 +00:00
import simplejson
2008-08-12 14:44:56 +00:00
2012-01-25 13:24:07 +00:00
_logger = logging . getLogger ( __name__ )
2006-12-07 13:41:40 +00:00
def _symbol_set ( symb ) :
2012-07-23 13:52:15 +00:00
if symb is None or symb == False :
2008-07-22 14:24:36 +00:00
return None
elif isinstance ( symb , unicode ) :
return symb . encode ( ' utf-8 ' )
return str ( symb )
2006-12-07 13:41:40 +00:00
2008-08-12 14:44:56 +00:00
2006-12-07 13:41:40 +00:00
class _column ( object ) :
2011-06-23 09:02:28 +00:00
""" Base of all fields, a database column
2011-08-26 10:35:16 +00:00
2011-06-23 09:02:28 +00:00
An instance of this object is a * description * of a database column . It will
not hold any data , but only provide the methods to manipulate data of an
ORM record or even prepare / update the database to hold such a field of data .
"""
2008-07-22 14:24:36 +00:00
_classic_read = True
_classic_write = True
2009-08-20 15:29:21 +00:00
_prefetch = True
2008-07-22 14:24:36 +00:00
_properties = False
_type = ' unknown '
_obj = None
2008-08-17 18:28:29 +00:00
_multi = False
2008-07-22 14:24:36 +00:00
_symbol_c = ' %s '
_symbol_f = _symbol_set
_symbol_set = ( _symbol_c , _symbol_f )
_symbol_get = None
2011-12-22 18:24:05 +00:00
# used to hide a certain field type in the list of field types
_deprecated = False
2011-09-26 11:12:26 +00:00
def __init__ ( self , string = ' unknown ' , required = False , readonly = False , domain = None , context = None , states = None , priority = 0 , change_default = False , size = None , ondelete = None , translate = False , select = False , manual = False , * * args ) :
2011-05-25 13:26:12 +00:00
"""
2011-05-25 14:59:09 +00:00
The ' manual ' keyword argument specifies if the field is a custom one .
It corresponds to the ' state ' column in ir_model_fields .
2011-05-25 13:26:12 +00:00
"""
2010-11-12 13:42:14 +00:00
if domain is None :
domain = [ ]
if context is None :
context = { }
2008-07-22 14:24:36 +00:00
self . states = states or { }
self . string = string
self . readonly = readonly
self . required = required
self . size = size
self . help = args . get ( ' help ' , ' ' )
self . priority = priority
self . change_default = change_default
2011-09-26 11:12:26 +00:00
self . ondelete = ondelete . lower ( ) if ondelete else None # defaults to 'set null' in ORM
2008-07-22 14:24:36 +00:00
self . translate = translate
2010-11-12 13:42:14 +00:00
self . _domain = domain
self . _context = context
2008-07-22 14:24:36 +00:00
self . write = False
self . read = False
self . view_load = 0
2008-08-12 14:44:56 +00:00
self . select = select
2011-05-25 13:26:12 +00:00
self . manual = manual
2009-09-17 07:27:12 +00:00
self . selectable = True
2010-03-08 09:40:36 +00:00
self . group_operator = args . get ( ' group_operator ' , False )
2012-05-18 14:36:25 +00:00
self . groups = False # CSV list of ext IDs of groups that can access this field
2008-07-22 14:24:36 +00:00
for a in args :
if args [ a ] :
setattr ( self , a , args [ a ] )
2012-05-18 14:36:25 +00:00
2008-07-22 14:24:36 +00:00
def restart ( self ) :
pass
def set ( self , cr , obj , id , name , value , user = None , context = None ) :
2008-12-09 13:35:40 +00:00
cr . execute ( ' update ' + obj . _table + ' set ' + name + ' = ' + self . _symbol_set [ 0 ] + ' where id= %s ' , ( self . _symbol_set [ 1 ] ( value ) , id ) )
2008-08-12 14:44:56 +00:00
2008-08-29 13:08:14 +00:00
def get ( self , cr , obj , ids , name , user = None , offset = 0 , context = None , values = None ) :
2008-08-12 14:44:56 +00:00
raise Exception ( _ ( ' undefined get method ! ' ) )
2008-07-22 14:24:36 +00:00
2010-01-05 12:03:39 +00:00
def search ( self , cr , obj , args , name , value , offset = 0 , limit = None , uid = None , context = None ) :
ids = obj . search ( cr , uid , args + self . _domain + [ ( name , ' ilike ' , value ) ] , offset , limit , context = context )
res = obj . read ( cr , uid , ids , [ name ] , context = context )
2008-07-22 14:24:36 +00:00
return [ x [ name ] for x in res ]
2008-08-12 14:44:56 +00:00
2012-08-02 17:02:44 +00:00
def as_display_name ( self , cr , uid , obj , value , context = None ) :
""" Converts a field value to a suitable string representation for a record,
e . g . when this field is used as ` ` rec_name ` ` .
: param obj : the ` ` BaseModel ` ` instance this column belongs to
: param value : a proper value as returned by : py : meth : ` ~ openerp . orm . osv . BaseModel . read `
for this column
"""
# delegated to class method, so a column type A can delegate
# to a column type B.
return self . _as_display_name ( self , cr , uid , obj , value , context = None )
@classmethod
def _as_display_name ( cls , field , cr , uid , obj , value , context = None ) :
# This needs to be a class method, in case a column type A as to delegate
# to a column type B.
return tools . ustr ( value )
2006-12-07 13:41:40 +00:00
# ---------------------------------------------------------
# Simple fields
# ---------------------------------------------------------
class boolean ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' boolean '
_symbol_c = ' %s '
_symbol_f = lambda x : x and ' True ' or ' False '
_symbol_set = ( _symbol_c , _symbol_f )
2006-12-07 13:41:40 +00:00
2011-10-21 14:28:36 +00:00
def __init__ ( self , string = ' unknown ' , required = False , * * args ) :
super ( boolean , self ) . __init__ ( string = string , required = required , * * args )
if required :
2012-02-09 09:43:42 +00:00
_logger . debug (
2012-01-25 13:24:07 +00:00
" required=True is deprecated: making a boolean field "
" `required` has no effect, as NULL values are "
" automatically turned into False. " )
2011-10-21 14:28:36 +00:00
2006-12-07 13:41:40 +00:00
class integer ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' integer '
2008-12-09 13:35:40 +00:00
_symbol_c = ' %s '
2008-07-22 14:24:36 +00:00
_symbol_f = lambda x : int ( x or 0 )
_symbol_set = ( _symbol_c , _symbol_f )
2010-04-18 20:07:32 +00:00
_symbol_get = lambda self , x : x or 0
2006-12-07 13:41:40 +00:00
2011-10-21 14:28:36 +00:00
def __init__ ( self , string = ' unknown ' , required = False , * * args ) :
super ( integer , self ) . __init__ ( string = string , required = required , * * args )
if required :
2012-02-09 09:43:42 +00:00
_logger . debug (
2012-01-25 13:24:07 +00:00
" required=True is deprecated: making an integer field "
" `required` has no effect, as NULL values are "
" automatically turned into 0. " )
2011-10-21 14:28:36 +00:00
2006-12-07 13:41:40 +00:00
class reference ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' reference '
2011-06-24 08:46:56 +00:00
_classic_read = False # post-process to handle missing target
2008-07-22 14:24:36 +00:00
def __init__ ( self , string , selection , size , * * args ) :
_column . __init__ ( self , string = string , size = size , selection = selection , * * args )
2006-12-07 13:41:40 +00:00
2011-06-24 08:46:56 +00:00
def get ( self , cr , obj , ids , name , uid = None , context = None , values = None ) :
result = { }
# copy initial values fetched previously.
2011-10-05 10:51:03 +00:00
for value in values :
2011-10-05 13:55:49 +00:00
result [ value [ ' id ' ] ] = value [ name ]
2011-10-05 10:51:03 +00:00
if value [ name ] :
model , res_id = value [ name ] . split ( ' , ' )
2011-10-05 13:55:49 +00:00
if not obj . pool . get ( model ) . exists ( cr , uid , [ int ( res_id ) ] , context = context ) :
2011-10-05 10:51:03 +00:00
result [ value [ ' id ' ] ] = False
2011-06-24 08:46:56 +00:00
return result
2008-08-12 14:44:56 +00:00
2012-08-02 17:02:44 +00:00
@classmethod
def _as_display_name ( cls , field , cr , uid , obj , value , context = None ) :
if value :
# reference fields have a 'model,id'-like value, that we need to convert
# to a real name
model_name , res_id = value . split ( ' , ' )
model = obj . pool . get ( model_name )
if model and res_id :
return model . name_get ( cr , uid , [ res_id ] , context = context ) [ 0 ] [ 1 ]
return tools . ustr ( value )
2006-12-07 13:41:40 +00:00
class char ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' char '
2008-08-12 14:44:56 +00:00
2012-07-23 13:49:31 +00:00
def __init__ ( self , string = " unknown " , size = None , * * args ) :
2012-07-23 14:05:02 +00:00
_column . __init__ ( self , string = string , size = size or None , * * args )
2008-07-22 14:24:36 +00:00
self . _symbol_set = ( self . _symbol_c , self . _symbol_set_char )
2008-08-12 14:44:56 +00:00
2008-07-22 14:24:36 +00:00
# takes a string (encoded in utf8) and returns a string (encoded in utf8)
def _symbol_set_char ( self , symb ) :
2008-08-12 14:44:56 +00:00
#TODO:
# * we need to remove the "symb==False" from the next line BUT
2008-07-22 14:24:36 +00:00
# for now too many things rely on this broken behavior
# * the symb==None test should be common to all data types
2012-07-23 13:52:15 +00:00
if symb is None or symb == False :
2008-07-22 14:24:36 +00:00
return None
2008-08-12 14:44:56 +00:00
# we need to convert the string to a unicode object to be able
2008-07-22 14:24:36 +00:00
# to evaluate its length (and possibly truncate it) reliably
2009-02-12 13:32:52 +00:00
u_symb = tools . ustr ( symb )
2008-11-24 16:22:47 +00:00
return u_symb [ : self . size ] . encode ( ' utf8 ' )
2006-12-07 13:41:40 +00:00
2008-08-12 14:44:56 +00:00
2006-12-07 13:41:40 +00:00
class text ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' text '
2006-12-07 13:41:40 +00:00
import __builtin__
class float ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' float '
2008-12-09 13:35:40 +00:00
_symbol_c = ' %s '
2008-07-22 14:24:36 +00:00
_symbol_f = lambda x : __builtin__ . float ( x or 0.0 )
_symbol_set = ( _symbol_c , _symbol_f )
2010-04-18 20:07:32 +00:00
_symbol_get = lambda self , x : x or 0.0
2008-08-12 14:44:56 +00:00
2011-10-21 14:28:36 +00:00
def __init__ ( self , string = ' unknown ' , digits = None , digits_compute = None , required = False , * * args ) :
_column . __init__ ( self , string = string , required = required , * * args )
2008-07-22 14:24:36 +00:00
self . digits = digits
2011-12-22 18:24:05 +00:00
# synopsis: digits_compute(cr) -> (precision, scale)
2010-03-06 19:59:55 +00:00
self . digits_compute = digits_compute
2011-10-21 14:28:36 +00:00
if required :
2012-02-09 09:43:42 +00:00
_logger . debug (
2012-01-25 13:24:07 +00:00
" required=True is deprecated: making a float field "
" `required` has no effect, as NULL values are "
" automatically turned into 0.0. " )
2006-12-07 13:41:40 +00:00
2010-03-06 19:59:55 +00:00
def digits_change ( self , cr ) :
if self . digits_compute :
2011-12-22 18:24:05 +00:00
self . digits = self . digits_compute ( cr )
if self . digits :
precision , scale = self . digits
self . _symbol_set = ( ' %s ' , lambda x : float_repr ( float_round ( __builtin__ . float ( x or 0.0 ) ,
precision_digits = scale ) ,
precision_digits = scale ) )
2008-08-12 14:44:56 +00:00
2006-12-07 13:41:40 +00:00
class date ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' date '
2012-02-13 18:03:55 +00:00
2010-10-12 16:01:30 +00:00
@staticmethod
def today ( * args ) :
""" Returns the current date in a format fit for being a
default value to a ` ` date ` ` field .
2006-12-07 13:41:40 +00:00
2010-10-12 16:01:30 +00:00
This method should be provided as is to the _defaults dict , it
should not be called .
"""
return DT . date . today ( ) . strftime (
tools . DEFAULT_SERVER_DATE_FORMAT )
2008-08-12 14:44:56 +00:00
2012-02-13 18:03:55 +00:00
@staticmethod
2012-02-14 12:24:13 +00:00
def context_today ( model , cr , uid , context = None , timestamp = None ) :
2012-02-13 18:03:55 +00:00
""" Returns the current date as seen in the client ' s timezone
in a format fit for date fields .
This method may be passed as value to initialize _defaults .
2012-02-14 12:24:13 +00:00
: param Model model : model ( osv ) for which the date value is being
computed - technical field , currently ignored ,
automatically passed when used in _defaults .
2012-02-14 08:50:44 +00:00
: param datetime timestamp : optional datetime value to use instead of
the current date and time ( must be a
datetime , regular dates can ' t be converted
2012-02-15 13:37:48 +00:00
between timezones . )
: param dict context : the ' tz ' key in the context should give the
name of the User / Client timezone ( otherwise
UTC is used )
: rtype : str
"""
2012-02-14 08:50:44 +00:00
today = timestamp or DT . datetime . now ( )
2012-02-13 18:03:55 +00:00
context_today = None
if context and context . get ( ' tz ' ) :
try :
utc = pytz . timezone ( ' UTC ' )
context_tz = pytz . timezone ( context [ ' tz ' ] )
2012-02-15 13:37:48 +00:00
utc_today = utc . localize ( today , is_dst = False ) # UTC = no DST
2012-02-13 18:03:55 +00:00
context_today = utc_today . astimezone ( context_tz )
except Exception :
_logger . debug ( " failed to compute context/client-specific today date, "
" using the UTC value for `today` " ,
exc_info = True )
return ( context_today or today ) . strftime ( tools . DEFAULT_SERVER_DATE_FORMAT )
2006-12-07 13:41:40 +00:00
class datetime ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' datetime '
2010-10-12 16:01:30 +00:00
@staticmethod
def now ( * args ) :
""" Returns the current datetime in a format fit for being a
default value to a ` ` datetime ` ` field .
2006-12-07 13:41:40 +00:00
2010-10-12 16:01:30 +00:00
This method should be provided as is to the _defaults dict , it
should not be called .
"""
return DT . datetime . now ( ) . strftime (
tools . DEFAULT_SERVER_DATETIME_FORMAT )
2008-08-12 14:44:56 +00:00
2012-02-15 13:37:48 +00:00
@staticmethod
def context_timestamp ( cr , uid , timestamp , context = None ) :
""" Returns the given timestamp converted to the client ' s timezone.
This method is * not * meant for use as a _defaults initializer ,
because datetime fields are automatically converted upon
display on client side . For _defaults you : meth : ` fields . datetime . now `
should be used instead .
: param datetime timestamp : naive datetime value ( expressed in UTC )
to be converted to the client timezone
: param dict context : the ' tz ' key in the context should give the
name of the User / Client timezone ( otherwise
UTC is used )
: rtype : datetime
: return : timestamp converted to timezone - aware datetime in context
timezone
"""
assert isinstance ( timestamp , DT . datetime ) , ' Datetime instance expected '
if context and context . get ( ' tz ' ) :
try :
utc = pytz . timezone ( ' UTC ' )
context_tz = pytz . timezone ( context [ ' tz ' ] )
utc_timestamp = utc . localize ( timestamp , is_dst = False ) # UTC = no DST
return utc_timestamp . astimezone ( context_tz )
except Exception :
_logger . debug ( " failed to compute context/client-specific timestamp, "
" using the UTC value " ,
exc_info = True )
return timestamp
2006-12-07 13:41:40 +00:00
class binary ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' binary '
_symbol_c = ' %s '
2012-02-22 09:39:37 +00:00
# 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 in symbol_f.
# This str coercion will only work for pure ASCII unicode strings,
# on purpose - non base64 data must be passed as a 8bit byte strings.
_symbol_f = lambda symb : symb and Binary ( str ( symb ) ) or None
2008-07-22 14:24:36 +00:00
_symbol_set = ( _symbol_c , _symbol_f )
2008-12-14 16:46:47 +00:00
_symbol_get = lambda self , x : x and str ( x )
2006-12-07 13:41:40 +00:00
2008-08-29 13:08:14 +00:00
_classic_read = False
2009-08-20 15:29:21 +00:00
_prefetch = False
2008-09-16 07:31:23 +00:00
def __init__ ( self , string = ' unknown ' , filters = None , * * args ) :
_column . __init__ ( self , string = string , * * args )
self . filters = filters
2011-08-16 11:02:34 +00:00
def get ( self , cr , obj , ids , name , user = None , context = None , values = None ) :
2008-08-29 13:08:14 +00:00
if not context :
context = { }
if not values :
values = [ ]
res = { }
for i in ids :
val = None
for v in values :
if v [ ' id ' ] == i :
val = v [ name ]
break
2010-11-04 17:05:23 +00:00
# If client is requesting only the size of the field, we return it instead
# of the content. Presumably a separate request will be done to read the actual
# content if it's needed at some point.
# TODO: after 6.0 we should consider returning a dict with size and content instead of
# having an implicit convention for the value
if val and context . get ( ' bin_size_ %s ' % name , context . get ( ' bin_size ' ) ) :
2009-02-03 21:58:14 +00:00
res [ i ] = tools . human_size ( long ( val ) )
2008-12-14 16:46:47 +00:00
else :
res [ i ] = val
2008-08-29 13:08:14 +00:00
return res
2006-12-07 13:41:40 +00:00
class selection ( _column ) :
2008-07-22 14:24:36 +00:00
_type = ' selection '
2008-08-12 14:44:56 +00:00
2008-07-22 14:24:36 +00:00
def __init__ ( self , selection , string = ' unknown ' , * * args ) :
_column . __init__ ( self , string = string , * * args )
self . selection = selection
2006-12-07 13:41:40 +00:00
# ---------------------------------------------------------
# Relationals fields
# ---------------------------------------------------------
#
# Values: (0, 0, { fields }) create
2010-06-16 17:58:37 +00:00
# (1, ID, { fields }) update
2006-12-07 13:41:40 +00:00
# (2, ID) remove (delete)
# (3, ID) unlink one (target id or target of relation)
# (4, ID) link
# (5) unlink all (only valid for one2many)
#
class many2one ( _column ) :
2008-07-22 14:24:36 +00:00
_classic_read = False
_classic_write = True
_type = ' many2one '
2008-12-14 16:46:47 +00:00
_symbol_c = ' %s '
_symbol_f = lambda x : x or None
_symbol_set = ( _symbol_c , _symbol_f )
2008-08-12 14:44:56 +00:00
2008-07-22 14:24:36 +00:00
def __init__ ( self , obj , string = ' unknown ' , * * args ) :
_column . __init__ ( self , string = string , * * args )
self . _obj = obj
def get ( self , cr , obj , ids , name , user = None , context = None , values = None ) :
2010-11-23 15:44:08 +00:00
if context is None :
context = { }
if values is None :
values = { }
2010-09-18 14:47:32 +00:00
2008-07-22 14:24:36 +00:00
res = { }
for r in values :
res [ r [ ' id ' ] ] = r [ name ]
for id in ids :
res . setdefault ( id , ' ' )
obj = obj . pool . get ( self . _obj )
2010-01-28 13:26:53 +00:00
2008-07-22 14:24:36 +00:00
# build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
2010-09-19 09:45:18 +00:00
# we use uid=1 because the visibility of a many2one field value (just id and name)
# must be the access right of the parent form and not the linked object itself.
2011-01-13 01:09:01 +00:00
records = dict ( obj . name_get ( cr , 1 ,
list ( set ( [ x for x in res . values ( ) if isinstance ( x , ( int , long ) ) ] ) ) ,
context = context ) )
2010-09-18 14:47:32 +00:00
for id in res :
2010-09-19 09:45:18 +00:00
if res [ id ] in records :
res [ id ] = ( res [ id ] , records [ res [ id ] ] )
2008-07-22 14:24:36 +00:00
else :
2010-09-18 14:47:32 +00:00
res [ id ] = False
2008-07-22 14:24:36 +00:00
return res
def set ( self , cr , obj_src , id , field , values , user = None , context = None ) :
if not context :
2008-08-12 14:44:56 +00:00
context = { }
2008-07-22 14:24:36 +00:00
obj = obj_src . pool . get ( self . _obj )
self . _table = obj_src . pool . get ( self . _obj ) . _table
2008-10-07 14:19:11 +00:00
if type ( values ) == type ( [ ] ) :
2008-07-22 14:24:36 +00:00
for act in values :
2008-08-12 14:44:56 +00:00
if act [ 0 ] == 0 :
2008-07-22 14:24:36 +00:00
id_new = obj . create ( cr , act [ 2 ] )
2008-12-09 13:35:40 +00:00
cr . execute ( ' update ' + obj_src . _table + ' set ' + field + ' = %s where id= %s ' , ( id_new , id ) )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 1 :
2008-07-22 14:24:36 +00:00
obj . write ( cr , [ act [ 1 ] ] , act [ 2 ] , context = context )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 2 :
2008-12-09 13:35:40 +00:00
cr . execute ( ' delete from ' + self . _table + ' where id= %s ' , ( act [ 1 ] , ) )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 3 or act [ 0 ] == 5 :
2008-12-09 13:35:40 +00:00
cr . execute ( ' update ' + obj_src . _table + ' set ' + field + ' =null where id= %s ' , ( id , ) )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 4 :
2008-12-09 13:35:40 +00:00
cr . execute ( ' update ' + obj_src . _table + ' set ' + field + ' = %s where id= %s ' , ( act [ 1 ] , id ) )
2008-07-22 14:24:36 +00:00
else :
if values :
2008-12-09 13:35:40 +00:00
cr . execute ( ' update ' + obj_src . _table + ' set ' + field + ' = %s where id= %s ' , ( values , id ) )
2008-07-22 14:24:36 +00:00
else :
2008-12-09 13:35:40 +00:00
cr . execute ( ' update ' + obj_src . _table + ' set ' + field + ' =null where id= %s ' , ( id , ) )
2008-07-22 14:24:36 +00:00
2010-01-05 12:03:39 +00:00
def search ( self , cr , obj , args , name , value , offset = 0 , limit = None , uid = None , context = None ) :
return obj . pool . get ( self . _obj ) . search ( cr , uid , args + self . _domain + [ ( ' name ' , ' like ' , value ) ] , offset , limit , context = context )
2008-08-12 14:44:56 +00:00
2012-08-02 17:02:44 +00:00
@classmethod
def _as_display_name ( cls , field , cr , uid , obj , value , context = None ) :
return value [ 1 ] if isinstance ( value , tuple ) else tools . ustr ( value )
2006-12-07 13:41:40 +00:00
2008-06-15 14:59:23 +00:00
class one2many ( _column ) :
2008-07-22 14:24:36 +00:00
_classic_read = False
_classic_write = False
2009-08-20 15:29:21 +00:00
_prefetch = False
2008-07-22 14:24:36 +00:00
_type = ' one2many '
2008-08-12 14:44:56 +00:00
2010-03-18 16:53:53 +00:00
def __init__ ( self , obj , fields_id , string = ' unknown ' , limit = None , * * args ) :
2008-07-22 14:24:36 +00:00
_column . __init__ ( self , string = string , * * args )
self . _obj = obj
self . _fields_id = fields_id
self . _limit = limit
#one2many can't be used as condition for defaults
assert ( self . change_default != True )
def get ( self , cr , obj , ids , name , user = None , offset = 0 , context = None , values = None ) :
2010-11-23 15:44:08 +00:00
if context is None :
2008-07-22 14:24:36 +00:00
context = { }
2009-04-06 16:05:07 +00:00
if self . _context :
context = context . copy ( )
context . update ( self . _context )
2010-11-23 15:44:08 +00:00
if values is None :
2008-07-22 14:24:36 +00:00
values = { }
2010-06-11 15:53:17 +00:00
2010-10-13 15:00:40 +00:00
res = { }
for id in ids :
res [ id ] = [ ]
2010-06-11 15:53:17 +00:00
2010-06-11 15:52:14 +00:00
ids2 = obj . pool . get ( self . _obj ) . search ( cr , user , self . _domain + [ ( self . _fields_id , ' in ' , ids ) ] , limit = self . _limit , context = context )
2008-07-22 14:24:36 +00:00
for r in obj . pool . get ( self . _obj ) . _read_flat ( cr , user , ids2 , [ self . _fields_id ] , context = context , load = ' _classic_write ' ) :
2010-10-13 16:39:27 +00:00
if r [ self . _fields_id ] in res :
2010-10-13 15:00:40 +00:00
res [ r [ self . _fields_id ] ] . append ( r [ ' id ' ] )
2008-07-22 14:24:36 +00:00
return res
def set ( self , cr , obj , id , field , values , user = None , context = None ) :
2009-08-20 15:29:21 +00:00
result = [ ]
2008-07-22 14:24:36 +00:00
if not context :
2008-08-12 14:44:56 +00:00
context = { }
2009-04-06 16:05:07 +00:00
if self . _context :
context = context . copy ( )
context . update ( self . _context )
2009-08-20 15:29:21 +00:00
context [ ' no_store_function ' ] = True
2008-07-22 14:24:36 +00:00
if not values :
return
_table = obj . pool . get ( self . _obj ) . _table
obj = obj . pool . get ( self . _obj )
for act in values :
2008-08-12 14:44:56 +00:00
if act [ 0 ] == 0 :
2008-07-22 14:24:36 +00:00
act [ 2 ] [ self . _fields_id ] = id
2009-08-20 15:29:21 +00:00
id_new = obj . create ( cr , user , act [ 2 ] , context = context )
result + = obj . _store_get_values ( cr , user , [ id_new ] , act [ 2 ] . keys ( ) , context )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 1 :
obj . write ( cr , user , [ act [ 1 ] ] , act [ 2 ] , context = context )
elif act [ 0 ] == 2 :
2008-07-22 14:24:36 +00:00
obj . unlink ( cr , user , [ act [ 1 ] ] , context = context )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 3 :
2011-07-05 08:51:06 +00:00
reverse_rel = obj . _all_columns . get ( self . _fields_id )
assert reverse_rel , ' Trying to unlink the content of a o2m but the pointed model does not have a m2o '
# if the model has on delete cascade, just delete the row
if reverse_rel . column . ondelete == " cascade " :
obj . unlink ( cr , user , [ act [ 1 ] ] , context = context )
else :
cr . execute ( ' update ' + _table + ' set ' + self . _fields_id + ' =null where id= %s ' , ( act [ 1 ] , ) )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 4 :
2011-02-05 00:43:16 +00:00
# Must use write() to recompute parent_store structure if needed
2011-02-07 10:26:52 +00:00
obj . write ( cr , user , [ act [ 1 ] ] , { self . _fields_id : id } , context = context or { } )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 5 :
2011-06-29 16:27:46 +00:00
reverse_rel = obj . _all_columns . get ( self . _fields_id )
2011-07-05 08:51:06 +00:00
assert reverse_rel , ' Trying to unlink the content of a o2m but the pointed model does not have a m2o '
2012-01-09 14:04:32 +00:00
# if the o2m has a static domain we must respect it when unlinking
extra_domain = self . _domain if isinstance ( getattr ( self , ' _domain ' , None ) , list ) else [ ]
ids_to_unlink = obj . search ( cr , user , [ ( self . _fields_id , ' = ' , id ) ] + extra_domain , context = context )
# If the model has cascade deletion, we delete the rows because it is the intended behavior,
# otherwise we only nullify the reverse foreign key column.
2011-06-29 16:27:46 +00:00
if reverse_rel . column . ondelete == " cascade " :
2012-01-09 14:04:32 +00:00
obj . unlink ( cr , user , ids_to_unlink , context = context )
2011-06-29 16:27:46 +00:00
else :
2012-01-09 14:04:32 +00:00
obj . write ( cr , user , ids_to_unlink , { self . _fields_id : False } , context = context )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 6 :
2011-02-05 00:43:16 +00:00
# Must use write() to recompute parent_store structure if needed
2009-04-27 18:17:01 +00:00
obj . write ( cr , user , act [ 2 ] , { self . _fields_id : id } , context = context or { } )
ids2 = act [ 2 ] or [ 0 ]
2010-06-25 08:49:06 +00:00
cr . execute ( ' select id from ' + _table + ' where ' + self . _fields_id + ' = %s and id <> ALL ( %s ) ' , ( id , ids2 ) )
2009-04-27 18:17:01 +00:00
ids3 = map ( lambda x : x [ 0 ] , cr . fetchall ( ) )
obj . write ( cr , user , ids3 , { self . _fields_id : False } , context = context or { } )
2009-08-20 15:29:21 +00:00
return result
2008-08-12 14:44:56 +00:00
2010-01-05 12:03:39 +00:00
def search ( self , cr , obj , args , name , value , offset = 0 , limit = None , uid = None , operator = ' like ' , context = None ) :
return obj . pool . get ( self . _obj ) . name_search ( cr , uid , value , self . _domain , operator , context = context , limit = limit )
2006-12-07 13:41:40 +00:00
2012-08-02 17:02:44 +00:00
@classmethod
def _as_display_name ( cls , field , cr , uid , obj , value , context = None ) :
raise NotImplementedError ( ' One2Many columns should not be used as record name (_rec_name) ' )
2008-08-12 14:44:56 +00:00
2006-12-07 13:41:40 +00:00
#
# Values: (0, 0, { fields }) create
2010-06-16 17:58:37 +00:00
# (1, ID, { fields }) update (write fields to ID)
# (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
# (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
# (4, ID) link (add a relationship)
2006-12-07 13:41:40 +00:00
# (5, ID) unlink all
# (6, ?, ids) set a list of links
#
class many2many ( _column ) :
2011-09-26 17:14:51 +00:00
""" Encapsulates the logic of a many-to-many bidirectional relationship, handling the
low - level details of the intermediary relationship table transparently .
2011-10-03 13:26:53 +00:00
A many - to - many relationship is always symmetrical , and can be declared and accessed
from either endpoint model .
If ` ` rel ` ` ( relationship table name ) , ` ` id1 ` ` ( source foreign key column name )
or id2 ( destination foreign key column name ) are not specified , the system will
provide default values . This will by default only allow one single symmetrical
many - to - many relationship between the source and destination model .
For multiple many - to - many relationship between the same models and for
relationships where source and destination models are the same , ` ` rel ` ` , ` ` id1 ` `
and ` ` id2 ` ` should be specified explicitly .
2011-09-26 17:14:51 +00:00
: param str obj : destination model
: param str rel : optional name of the intermediary relationship table . If not specified ,
a canonical name will be derived based on the alphabetically - ordered
model names of the source and destination ( in the form : ` ` amodel_bmodel_rel ` ` ) .
Automatic naming is not possible when the source and destination are
the same , for obvious ambiguity reasons .
: param str id1 : optional name for the column holding the foreign key to the current
model in the relationship table . If not specified , a canonical name
will be derived based on the model name ( in the form : ` src_model_id ` ) .
: param str id2 : optional name for the column holding the foreign key to the destination
model in the relationship table . If not specified , a canonical name
will be derived based on the model name ( in the form : ` dest_model_id ` )
: param str string : field label
"""
2008-07-22 14:24:36 +00:00
_classic_read = False
_classic_write = False
2009-08-20 15:29:21 +00:00
_prefetch = False
2008-07-22 14:24:36 +00:00
_type = ' many2many '
2011-09-26 17:14:51 +00:00
def __init__ ( self , obj , rel = None , id1 = None , id2 = None , string = ' unknown ' , limit = None , * * args ) :
2011-10-05 06:51:55 +00:00
"""
2011-09-26 17:14:51 +00:00
"""
2008-07-22 14:24:36 +00:00
_column . __init__ ( self , string = string , * * args )
self . _obj = obj
2011-09-26 17:14:51 +00:00
if rel and ' . ' in rel :
2008-10-28 23:33:20 +00:00
raise Exception ( _ ( ' The second argument of the many2many field %s must be a SQL table ! ' \
' You used %s , which is not a valid SQL table name. ' ) % ( string , rel ) )
2008-07-22 14:24:36 +00:00
self . _rel = rel
self . _id1 = id1
self . _id2 = id2
self . _limit = limit
2011-09-26 17:14:51 +00:00
def _sql_names ( self , source_model ) :
""" Return the SQL names defining the structure of the m2m relationship table
2011-10-05 06:51:55 +00:00
2011-09-26 17:14:51 +00:00
: return : ( m2m_table , local_col , dest_col ) where m2m_table is the table name ,
local_col is the name of the column holding the current model ' s FK, and
dest_col is the name of the column holding the destination model ' s FK, and
"""
tbl , col1 , col2 = self . _rel , self . _id1 , self . _id2
if not all ( ( tbl , col1 , col2 ) ) :
# the default table name is based on the stable alphabetical order of tables
dest_model = source_model . pool . get ( self . _obj )
tables = tuple ( sorted ( [ source_model . _table , dest_model . _table ] ) )
if not tbl :
assert tables [ 0 ] != tables [ 1 ] , ' Implicit/Canonical naming of m2m relationship table ' \
' is not possible when source and destination models are ' \
' the same '
tbl = ' %s _ %s _rel ' % tables
if not col1 :
col1 = ' %s _id ' % source_model . _table
if not col2 :
col2 = ' %s _id ' % dest_model . _table
return ( tbl , col1 , col2 )
def get ( self , cr , model , ids , name , user = None , offset = 0 , context = None , values = None ) :
2008-07-22 14:24:36 +00:00
if not context :
2008-08-12 14:44:56 +00:00
context = { }
2008-07-22 14:24:36 +00:00
if not values :
2008-08-12 14:44:56 +00:00
values = { }
2008-07-22 14:24:36 +00:00
res = { }
if not ids :
return res
for id in ids :
res [ id ] = [ ]
2010-10-21 13:00:45 +00:00
if offset :
2012-01-25 13:24:07 +00:00
_logger . warning (
" Specifying offset at a many2many.get() is deprecated and may "
" produce unpredictable results. " )
2011-09-26 17:14:51 +00:00
obj = model . pool . get ( self . _obj )
rel , id1 , id2 = self . _sql_names ( model )
2008-07-22 14:24:36 +00:00
2010-12-31 06:13:14 +00:00
# static domains are lists, and are evaluated both here and on client-side, while string
2010-10-21 16:31:31 +00:00
# domains supposed by dynamic and evaluated on client-side only (thus ignored here)
# FIXME: make this distinction explicit in API!
domain = isinstance ( self . _domain , list ) and self . _domain or [ ]
wquery = obj . _where_calc ( cr , user , domain , context = context )
2010-10-21 13:00:45 +00:00
obj . _apply_ir_rules ( cr , user , wquery , ' read ' , context = context )
from_c , where_c , where_params = wquery . get_sql ( )
if where_c :
where_c = ' AND ' + where_c
if offset or self . _limit :
order_by = ' ORDER BY " %s " . %s ' % ( obj . _table , obj . _order . split ( ' , ' ) [ 0 ] )
else :
order_by = ' '
limit_str = ' '
if self . _limit is not None :
limit_str = ' LIMIT %d ' % self . _limit
2010-06-15 13:27:22 +00:00
query = ' SELECT %(rel)s . %(id2)s , %(rel)s . %(id1)s \
2010-10-21 13:00:45 +00:00
FROM % ( rel ) s , % ( from_c ) s \
WHERE % ( rel ) s . % ( id1 ) s IN % % s \
2010-06-15 13:27:22 +00:00
AND % ( rel ) s . % ( id2 ) s = % ( tbl ) s . id \
2010-10-21 13:00:45 +00:00
% ( where_c ) s \
% ( order_by ) s \
2010-06-15 13:27:22 +00:00
% ( limit ) s \
OFFSET % ( offset ) d ' \
2011-09-26 17:14:51 +00:00
% { ' rel ' : rel ,
2010-10-21 13:00:45 +00:00
' from_c ' : from_c ,
2010-06-15 13:27:22 +00:00
' tbl ' : obj . _table ,
2011-09-26 17:14:51 +00:00
' id1 ' : id1 ,
' id2 ' : id2 ,
2010-10-21 13:00:45 +00:00
' where_c ' : where_c ,
2010-06-15 13:27:22 +00:00
' limit ' : limit_str ,
2010-10-21 13:00:45 +00:00
' order_by ' : order_by ,
2010-06-15 13:27:22 +00:00
' offset ' : offset ,
}
2010-10-21 13:00:45 +00:00
cr . execute ( query , [ tuple ( ids ) , ] + where_params )
2008-07-22 14:24:36 +00:00
for r in cr . fetchall ( ) :
res [ r [ 1 ] ] . append ( r [ 0 ] )
return res
2011-09-26 17:14:51 +00:00
def set ( self , cr , model , id , name , values , user = None , context = None ) :
2008-07-22 14:24:36 +00:00
if not context :
2008-08-12 14:44:56 +00:00
context = { }
2008-07-22 14:24:36 +00:00
if not values :
return
2011-09-26 17:14:51 +00:00
rel , id1 , id2 = self . _sql_names ( model )
obj = model . pool . get ( self . _obj )
2008-07-22 14:24:36 +00:00
for act in values :
2009-11-11 05:50:28 +00:00
if not ( isinstance ( act , list ) or isinstance ( act , tuple ) ) or not act :
continue
2008-08-12 14:44:56 +00:00
if act [ 0 ] == 0 :
2011-06-24 14:01:03 +00:00
idnew = obj . create ( cr , user , act [ 2 ] , context = context )
2011-09-26 17:14:51 +00:00
cr . execute ( ' insert into ' + rel + ' ( ' + id1 + ' , ' + id2 + ' ) values ( %s , %s ) ' , ( id , idnew ) )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 1 :
obj . write ( cr , user , [ act [ 1 ] ] , act [ 2 ] , context = context )
elif act [ 0 ] == 2 :
2008-07-22 14:24:36 +00:00
obj . unlink ( cr , user , [ act [ 1 ] ] , context = context )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 3 :
2011-09-26 17:14:51 +00:00
cr . execute ( ' delete from ' + rel + ' where ' + id1 + ' = %s and ' + id2 + ' = %s ' , ( id , act [ 1 ] ) )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 4 :
2010-12-14 13:28:40 +00:00
# following queries are in the same transaction - so should be relatively safe
2011-09-26 17:14:51 +00:00
cr . execute ( ' SELECT 1 FROM ' + rel + ' WHERE ' + id1 + ' = %s and ' + id2 + ' = %s ' , ( id , act [ 1 ] ) )
2010-12-14 13:28:40 +00:00
if not cr . fetchone ( ) :
2011-09-26 17:14:51 +00:00
cr . execute ( ' insert into ' + rel + ' ( ' + id1 + ' , ' + id2 + ' ) values ( %s , %s ) ' , ( id , act [ 1 ] ) )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 5 :
2011-09-26 17:14:51 +00:00
cr . execute ( ' delete from ' + rel + ' where ' + id1 + ' = %s ' , ( id , ) )
2008-08-12 14:44:56 +00:00
elif act [ 0 ] == 6 :
2008-07-22 14:24:36 +00:00
2009-12-22 20:56:00 +00:00
d1 , d2 , tables = obj . pool . get ( ' ir.rule ' ) . domain_get ( cr , user , obj . _name , context = context )
2008-07-22 14:24:36 +00:00
if d1 :
2009-12-22 21:22:43 +00:00
d1 = ' and ' + ' and ' . join ( d1 )
2009-12-22 21:24:26 +00:00
else :
d1 = ' '
2011-09-26 17:14:51 +00:00
cr . execute ( ' delete from ' + rel + ' where ' + id1 + ' = %s AND ' + id2 + ' IN (SELECT ' + rel + ' . ' + id2 + ' FROM ' + rel + ' , ' + ' , ' . join ( tables ) + ' WHERE ' + rel + ' . ' + id1 + ' = %s AND ' + rel + ' . ' + id2 + ' = ' + obj . _table + ' .id ' + d1 + ' ) ' , [ id , id ] + d2 )
2008-07-22 14:24:36 +00:00
2008-08-12 14:44:56 +00:00
for act_nbr in act [ 2 ] :
2011-09-26 17:14:51 +00:00
cr . execute ( ' insert into ' + rel + ' ( ' + id1 + ' , ' + id2 + ' ) values ( %s , %s ) ' , ( id , act_nbr ) )
2008-07-22 14:24:36 +00:00
#
# TODO: use a name_search
#
2010-01-05 12:03:39 +00:00
def search ( self , cr , obj , args , name , value , offset = 0 , limit = None , uid = None , operator = ' like ' , context = None ) :
return obj . pool . get ( self . _obj ) . search ( cr , uid , args + self . _domain + [ ( ' name ' , operator , value ) ] , offset , limit , context = context )
2008-07-22 14:24:36 +00:00
2012-08-02 17:02:44 +00:00
@classmethod
def _as_display_name ( cls , field , cr , uid , obj , value , context = None ) :
raise NotImplementedError ( ' Many2Many columns should not be used as record name (_rec_name) ' )
2008-06-15 14:59:23 +00:00
2011-06-15 08:47:17 +00:00
def get_nice_size ( value ) :
2011-05-23 09:01:13 +00:00
size = 0
2011-06-15 08:47:17 +00:00
if isinstance ( value , ( int , long ) ) :
size = value
2011-06-15 15:26:59 +00:00
elif value : # this is supposed to be a string
2011-06-15 08:47:17 +00:00
size = len ( value )
return tools . human_size ( size )
2009-09-22 09:30:46 +00:00
2011-09-06 17:31:40 +00:00
# See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
# and http://bugs.python.org/issue10066
invalid_xml_low_bytes = re . compile ( r ' [ \ x00- \ x08 \ x0b- \ x0c \ x0e- \ x1f] ' )
2011-06-15 08:47:17 +00:00
def sanitize_binary_value ( value ) :
2010-12-09 23:07:42 +00:00
# binary fields should be 7-bit ASCII base64-encoded data,
# but we do additional sanity checks to make sure the values
2011-09-06 17:31:40 +00:00
# are not something else that won't pass via XML-RPC
2010-12-09 23:07:42 +00:00
if isinstance ( value , ( xmlrpclib . Binary , tuple , list , dict ) ) :
# these builtin types are meant to pass untouched
2011-06-15 08:47:17 +00:00
return value
2010-12-09 23:07:42 +00:00
2011-09-06 17:31:40 +00:00
# Handle invalid bytes values that will cause problems
# for XML-RPC. See for more info:
2010-12-09 23:07:42 +00:00
# - http://bugs.python.org/issue10066
# - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
2011-09-06 17:31:40 +00:00
# Coercing to unicode would normally allow it to properly pass via
# XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
# (this works for _any_ byte values, thanks to the fallback
# to latin-1 passthrough encoding when decoding to unicode)
value = tools . ustr ( value )
# Due to Python bug #10066 this could still yield invalid XML
# bytes, specifically in the low byte range, that will crash
# the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
# So check for low bytes values, and if any, perform
# base64 encoding - not very smart or useful, but this is
# our last resort to avoid crashing the request.
if invalid_xml_low_bytes . search ( value ) :
# b64-encode after restoring the pure bytes with latin-1
# passthrough encoding
value = base64 . b64encode ( value . encode ( ' latin-1 ' ) )
return value
2010-12-09 23:07:42 +00:00
2006-12-07 13:41:40 +00:00
# ---------------------------------------------------------
# Function fields
# ---------------------------------------------------------
class function ( _column ) :
2011-07-01 23:23:28 +00:00
"""
A field whose value is computed by a function ( rather
than being read from the database ) .
: param fnct : the callable that will compute the field value .
: param arg : arbitrary value to be passed to ` ` fnct ` ` when computing the value .
: param fnct_inv : the callable that will allow writing values in that field
( if not provided , the field is read - only ) .
: param fnct_inv_arg : arbitrary value to be passed to ` ` fnct_inv ` ` when
writing a value .
: param str type : type of the field simulated by the function field
: param fnct_search : the callable that allows searching on the field
( if not provided , search will not return any result ) .
: param store : store computed value in database
( see : ref : ` The * store * parameter < field - function - store > ` ) .
: type store : True or dict specifying triggers for field computation
: param multi : name of batch for batch computation of function fields .
All fields with the same batch name will be computed by
a single function call . This changes the signature of the
` ` fnct ` ` callable .
. . _field - function - fnct : The ` ` fnct ` ` parameter
. . rubric : : The ` ` fnct ` ` parameter
The callable implementing the function field must have the following signature :
. . function : : fnct ( model , cr , uid , ids , field_name ( s ) , arg , context )
Implements the function field .
2011-08-12 14:31:06 +00:00
: param orm model : model to which the field belongs ( should be ` ` self ` ` for
a model method )
2011-07-01 23:23:28 +00:00
: param field_name ( s ) : name of the field to compute , or if ` ` multi ` ` is provided ,
list of field names to compute .
: type field_name ( s ) : str | [ str ]
: param arg : arbitrary value passed when declaring the function field
: rtype : dict
: return : mapping of ` ` ids ` ` to computed values , or if multi is provided ,
to a map of field_names to computed values
The values in the returned dictionary must be of the type specified by the type
argument in the field declaration .
Here is an example with a simple function ` ` char ` ` function field : :
# declarations
def compute ( self , cr , uid , ids , field_name , arg , context ) :
result = { }
# ...
return result
_columns [ ' my_char ' ] = fields . function ( compute , type = ' char ' , size = 50 )
# when called with ``ids=[1,2,3]``, ``compute`` could return:
{
1 : ' foo ' ,
2 : ' bar ' ,
3 : False # null values should be returned explicitly too
}
If ` ` multi ` ` is set , then ` ` field_name ` ` is replaced by ` ` field_names ` ` : a list
of the field names that should be computed . Each value in the returned
dictionary must then be a dictionary mapping field names to values .
Here is an example where two function fields ( ` ` name ` ` and ` ` age ` ` )
are both computed by a single function field : :
# declarations
def compute ( self , cr , uid , ids , field_names , arg , context ) :
result = { }
# ...
return result
_columns [ ' name ' ] = fields . function ( compute_person_data , type = ' char ' , \
size = 50 , multi = ' person_data ' )
_columns [ ' ' age ' ] = fields.function(compute_person_data, type= ' integer ' , \
multi = ' person_data ' )
# when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
{
1 : { ' name ' : ' Bob ' , ' age ' : 23 } ,
2 : { ' name ' : ' Sally ' , ' age ' : 19 } ,
3 : { ' name ' : ' unknown ' , ' age ' : False }
}
. . _field - function - fnct - inv :
. . rubric : : The ` ` fnct_inv ` ` parameter
This callable implements the write operation for the function field
and must have the following signature :
2011-08-25 12:30:49 +00:00
. . function : : fnct_inv ( model , cr , uid , id , field_name , field_value , fnct_inv_arg , context )
2011-07-01 23:23:28 +00:00
Callable that implements the ` ` write ` ` operation for the function field .
2011-08-12 14:31:06 +00:00
: param orm model : model to which the field belongs ( should be ` ` self ` ` for
a model method )
2011-08-25 12:30:49 +00:00
: param int id : the identifier of the object to write on
2011-07-01 23:23:28 +00:00
: param str field_name : name of the field to set
: param fnct_inv_arg : arbitrary value passed when declaring the function field
: return : True
When writing values for a function field , the ` ` multi ` ` parameter is ignored .
. . _field - function - fnct - search :
. . rubric : : The ` ` fnct_search ` ` parameter
This callable implements the search operation for the function field
and must have the following signature :
. . function : : fnct_search ( model , cr , uid , model_again , field_name , criterion , context )
Callable that implements the ` ` search ` ` operation for the function field by expanding
a search criterion based on the function field into a new domain based only on
columns that are stored in the database .
2011-08-12 14:31:06 +00:00
: param orm model : model to which the field belongs ( should be ` ` self ` ` for
a model method )
: param orm model_again : same value as ` ` model ` ` ( seriously ! this is for backwards
compatibility )
2011-07-01 23:23:28 +00:00
: param str field_name : name of the field to search on
: param list criterion : domain component specifying the search criterion on the field .
: rtype : list
: return : domain to use instead of ` ` criterion ` ` when performing the search .
This new domain must be based only on columns stored in the database , as it
will be used directly without any translation .
The returned value must be a domain , that is , a list of the form [ ( field_name , operator , operand ) ] .
The most generic way to implement ` ` fnct_search ` ` is to directly search for the records that
match the given ` ` criterion ` ` , and return their ` ` ids ` ` wrapped in a domain , such as
` ` [ ( ' id ' , ' in ' , [ 1 , 3 , 5 ] ) ] ` ` .
. . _field - function - store :
. . rubric : : The ` ` store ` ` parameter
The ` ` store ` ` parameter allows caching the result of the field computation in the
database , and defining the triggers that will invalidate that cache and force a
recomputation of the function field .
When not provided , the field is computed every time its value is read .
The value of ` ` store ` ` may be either ` ` True ` ` ( to recompute the field value whenever
any field in the same record is modified ) , or a dictionary specifying a more
flexible set of recomputation triggers .
A trigger specification is a dictionary that maps the names of the models that
will trigger the computation , to a tuple describing the trigger rule , in the
following form : :
store = {
' trigger_model ' : ( mapping_function ,
[ ' trigger_field1 ' , ' trigger_field2 ' ] ,
priority ) ,
}
A trigger rule is defined by a 3 - item tuple where :
* The ` ` mapping_function ` ` is defined as follows :
. . function : : mapping_function ( trigger_model , cr , uid , trigger_ids , context )
Callable that maps record ids of a trigger model to ids of the
corresponding records in the source model ( whose field values
need to be recomputed ) .
2011-08-12 14:31:06 +00:00
: param orm model : trigger_model
2011-07-01 23:23:28 +00:00
: param list trigger_ids : ids of the records of trigger_model that were
modified
: rtype : list
: return : list of ids of the source model whose function field values
need to be recomputed
* The second item is a list of the fields who should act as triggers for
the computation . If an empty list is given , all fields will act as triggers .
* The last item is the priority , used to order the triggers when processing them
after any write operation on a model that has function field triggers . The
default priority is 10.
In fact , setting store = True is the same as using the following trigger dict : :
store = {
' model_itself ' : ( lambda self , cr , uid , ids , context : ids ,
[ ] ,
10 )
}
"""
2008-07-22 14:24:36 +00:00
_classic_read = False
_classic_write = False
2009-08-20 15:29:21 +00:00
_prefetch = False
2008-07-22 14:24:36 +00:00
_type = ' function '
_properties = True
2008-08-12 14:44:56 +00:00
2008-08-17 18:28:29 +00:00
#
# multi: compute several fields in one call
#
2011-07-01 23:23:28 +00:00
def __init__ ( self , fnct , arg = None , fnct_inv = None , fnct_inv_arg = None , type = ' float ' , fnct_search = None , obj = None , store = False , multi = False , * * args ) :
2008-07-22 14:24:36 +00:00
_column . __init__ ( self , * * args )
self . _obj = obj
self . _fnct = fnct
self . _fnct_inv = fnct_inv
self . _arg = arg
2008-08-17 18:28:29 +00:00
self . _multi = multi
2008-07-22 14:24:36 +00:00
if ' relation ' in args :
self . _obj = args [ ' relation ' ]
2010-03-08 09:40:36 +00:00
2010-03-06 20:51:54 +00:00
self . digits = args . get ( ' digits ' , ( 16 , 2 ) )
self . digits_compute = args . get ( ' digits_compute ' , None )
2008-07-22 14:24:36 +00:00
self . _fnct_inv_arg = fnct_inv_arg
if not fnct_inv :
self . readonly = 1
self . _type = type
self . _fnct_search = fnct_search
self . store = store
2009-09-17 07:27:12 +00:00
if not fnct_search and not store :
self . selectable = False
2010-03-08 09:40:36 +00:00
2008-12-07 02:16:54 +00:00
if store :
2010-08-13 00:23:21 +00:00
if self . _type != ' many2one ' :
# m2o fields need to return tuples with name_get, not just foreign keys
self . _classic_read = True
2008-12-07 02:16:54 +00:00
self . _classic_write = True
2008-12-16 08:55:33 +00:00
if type == ' binary ' :
self . _symbol_get = lambda x : x and str ( x )
2008-07-22 14:24:36 +00:00
if type == ' float ' :
2008-12-09 13:35:40 +00:00
self . _symbol_c = float . _symbol_c
self . _symbol_f = float . _symbol_f
self . _symbol_set = float . _symbol_set
2008-08-12 14:44:56 +00:00
2010-04-29 12:01:58 +00:00
if type == ' boolean ' :
self . _symbol_c = boolean . _symbol_c
self . _symbol_f = boolean . _symbol_f
self . _symbol_set = boolean . _symbol_set
2012-03-22 16:38:50 +00:00
if type == ' integer ' :
2011-01-10 09:44:13 +00:00
self . _symbol_c = integer . _symbol_c
self . _symbol_f = integer . _symbol_f
self . _symbol_set = integer . _symbol_set
2010-03-06 20:51:54 +00:00
def digits_change ( self , cr ) :
2011-12-22 18:24:05 +00:00
if self . _type == ' float ' :
if self . digits_compute :
self . digits = self . digits_compute ( cr )
if self . digits :
precision , scale = self . digits
self . _symbol_set = ( ' %s ' , lambda x : float_repr ( float_round ( __builtin__ . float ( x or 0.0 ) ,
precision_digits = scale ) ,
precision_digits = scale ) )
2010-03-06 20:51:54 +00:00
2010-01-05 12:03:39 +00:00
def search ( self , cr , uid , obj , name , args , context = None ) :
2008-07-22 14:24:36 +00:00
if not self . _fnct_search :
#CHECKME: should raise an exception
return [ ]
2010-01-05 12:03:39 +00:00
return self . _fnct_search ( obj , cr , uid , obj , name , args , context = context )
2008-07-22 14:24:36 +00:00
2011-06-15 10:25:51 +00:00
def postprocess ( self , cr , uid , obj , field , value = None , context = None ) :
2010-11-23 15:44:08 +00:00
if context is None :
2008-08-12 14:44:56 +00:00
context = { }
2011-06-13 11:19:49 +00:00
result = value
2011-06-15 08:47:17 +00:00
field_type = obj . _columns [ field ] . _type
2011-06-15 10:25:51 +00:00
if field_type == " many2one " :
2011-06-15 15:26:59 +00:00
# make the result a tuple if it is not already one
2011-06-15 08:47:17 +00:00
if isinstance ( value , ( int , long ) ) and hasattr ( obj . _columns [ field ] , ' relation ' ) :
obj_model = obj . pool . get ( obj . _columns [ field ] . relation )
2011-06-15 10:25:51 +00:00
dict_names = dict ( obj_model . name_get ( cr , uid , [ value ] , context ) )
2011-06-15 15:26:59 +00:00
result = ( value , dict_names [ value ] )
2011-06-15 08:47:17 +00:00
if field_type == ' binary ' :
2011-11-28 12:45:35 +00:00
if context . get ( ' bin_size ' ) :
2010-12-09 23:07:42 +00:00
# client requests only the size of binary fields
2011-06-13 11:19:49 +00:00
result = get_nice_size ( value )
2011-11-28 10:08:36 +00:00
elif not context . get ( ' bin_raw ' ) :
2011-06-13 11:19:49 +00:00
result = sanitize_binary_value ( value )
2012-03-22 16:38:50 +00:00
if field_type == " integer " and value > xmlrpclib . MAXINT :
2011-08-18 12:05:50 +00:00
# integer/long values greater than 2^31-1 are not supported
# in pure XMLRPC, so we have to pass them as floats :-(
# This is not needed for stored fields and non-functional integer
# fields, as their values are constrained by the database backend
# to the same 32bits signed int limit.
2012-04-19 13:02:45 +00:00
result = __builtin__ . float ( value )
2011-06-13 11:19:49 +00:00
return result
2011-06-15 10:25:51 +00:00
def get ( self , cr , obj , ids , name , uid = False , context = None , values = None ) :
2011-07-01 23:23:28 +00:00
result = self . _fnct ( obj , cr , uid , ids , name , self . _arg , context )
2011-06-13 11:19:49 +00:00
for id in ids :
2011-06-15 08:47:17 +00:00
if self . _multi and id in result :
2011-06-13 11:19:49 +00:00
for field , value in result [ id ] . iteritems ( ) :
if value :
2011-06-15 10:25:51 +00:00
result [ id ] [ field ] = self . postprocess ( cr , uid , obj , field , value , context )
elif result . get ( id ) :
result [ id ] = self . postprocess ( cr , uid , obj , name , result [ id ] , context )
2011-06-13 11:19:49 +00:00
return result
2010-03-08 09:40:36 +00:00
2008-07-22 14:24:36 +00:00
def set ( self , cr , obj , id , name , value , user = None , context = None ) :
if not context :
2008-08-12 14:44:56 +00:00
context = { }
2008-07-22 14:24:36 +00:00
if self . _fnct_inv :
self . _fnct_inv ( obj , cr , user , id , name , value , self . _fnct_inv_arg , context )
2006-12-07 13:41:40 +00:00
2012-08-02 17:02:44 +00:00
@classmethod
def _as_display_name ( cls , field , cr , uid , obj , value , context = None ) :
# Function fields are supposed to emulate a basic field type,
# so they can delegate to the basic type for record name rendering
return globals ( ) [ field . _type ] . _as_display_name ( field , cr , uid , obj , value , context = context )
2008-09-18 11:28:57 +00:00
# ---------------------------------------------------------
# Related fields
# ---------------------------------------------------------
class related ( function ) :
2011-06-23 09:02:28 +00:00
""" Field that points to some data inside another field of the current record.
Example : :
_columns = {
' foo_id ' : fields . many2one ( ' my.foo ' , ' Foo ' ) ,
2011-10-13 09:34:23 +00:00
' bar ' : fields . related ( ' foo_id ' , ' frol ' , type = ' char ' , string = ' Frol of Foo ' ) ,
2011-06-23 09:02:28 +00:00
}
"""
2008-09-18 11:28:57 +00:00
2010-07-22 13:49:48 +00:00
def _fnct_search ( self , tobj , cr , uid , obj = None , name = None , domain = None , context = None ) :
2008-12-09 08:03:55 +00:00
self . _field_get2 ( cr , uid , obj , context )
i = len ( self . _arg ) - 1
sarg = name
while i > 0 :
if type ( sarg ) in [ type ( [ ] ) , type ( ( 1 , ) ) ] :
where = [ ( self . _arg [ i ] , ' in ' , sarg ) ]
2008-10-03 10:55:11 +00:00
else :
2008-12-09 08:03:55 +00:00
where = [ ( self . _arg [ i ] , ' = ' , sarg ) ]
if domain :
where = map ( lambda x : ( self . _arg [ i ] , x [ 1 ] , x [ 2 ] ) , domain )
domain = [ ]
sarg = obj . pool . get ( self . _relations [ i ] [ ' object ' ] ) . search ( cr , uid , where , context = context )
i - = 1
return [ ( self . _arg [ 0 ] , ' in ' , sarg ) ]
2008-12-12 13:05:11 +00:00
def _fnct_write ( self , obj , cr , uid , ids , field_name , values , args , context = None ) :
2010-11-12 14:10:41 +00:00
self . _field_get2 ( cr , uid , obj , context = context )
2010-08-10 09:59:48 +00:00
if type ( ids ) != type ( [ ] ) :
ids = [ ids ]
objlst = obj . browse ( cr , uid , ids )
for data in objlst :
t_id = data . id
t_data = data
for i in range ( len ( self . arg ) ) :
if not t_data : break
field_detail = self . _relations [ i ]
if not t_data [ self . arg [ i ] ] :
if self . _type not in ( ' one2many ' , ' many2many ' ) :
2010-01-25 12:34:53 +00:00
t_id = t_data [ ' id ' ]
2010-08-10 09:59:48 +00:00
t_data = False
elif field_detail [ ' type ' ] in ( ' one2many ' , ' many2many ' ) :
if self . _type != " many2one " :
t_id = t_data . id
t_data = t_data [ self . arg [ i ] ] [ 0 ]
else :
t_data = False
else :
t_id = t_data [ ' id ' ]
t_data = t_data [ self . arg [ i ] ]
else :
model = obj . pool . get ( self . _relations [ - 1 ] [ ' object ' ] )
model . write ( cr , uid , [ t_id ] , { args [ - 1 ] : values } , context = context )
2008-09-18 11:28:57 +00:00
2008-10-07 14:19:11 +00:00
def _fnct_read ( self , obj , cr , uid , ids , field_name , args , context = None ) :
2008-12-09 08:03:55 +00:00
self . _field_get2 ( cr , uid , obj , context )
2008-09-19 06:30:20 +00:00
if not ids : return { }
2008-10-07 14:19:11 +00:00
relation = obj . _name
2010-07-27 09:39:09 +00:00
if self . _type in ( ' one2many ' , ' many2many ' ) :
2010-09-22 09:23:49 +00:00
res = dict ( [ ( i , [ ] ) for i in ids ] )
2010-07-27 09:39:09 +00:00
else :
res = { } . fromkeys ( ids , False )
2009-12-22 20:56:00 +00:00
2010-06-12 22:35:30 +00:00
objlst = obj . browse ( cr , 1 , ids , context = context )
2008-09-19 06:30:20 +00:00
for data in objlst :
2008-12-17 05:44:27 +00:00
if not data :
continue
2008-10-07 14:19:11 +00:00
t_data = data
relation = obj . _name
2008-09-19 06:30:20 +00:00
for i in range ( len ( self . arg ) ) :
2008-12-09 08:03:55 +00:00
field_detail = self . _relations [ i ]
relation = field_detail [ ' object ' ]
2008-12-16 18:58:24 +00:00
try :
if not t_data [ self . arg [ i ] ] :
t_data = False
break
except :
2008-09-19 06:30:20 +00:00
t_data = False
break
2009-12-01 13:19:08 +00:00
if field_detail [ ' type ' ] in ( ' one2many ' , ' many2many ' ) and i != len ( self . arg ) - 1 :
2008-10-07 14:19:11 +00:00
t_data = t_data [ self . arg [ i ] ] [ 0 ]
2010-07-27 09:39:09 +00:00
elif t_data :
2008-10-07 14:19:11 +00:00
t_data = t_data [ self . arg [ i ] ]
2008-09-19 06:30:20 +00:00
if type ( t_data ) == type ( objlst [ 0 ] ) :
2009-01-02 12:01:53 +00:00
res [ data . id ] = t_data . id
2010-07-27 09:39:09 +00:00
elif t_data :
2008-10-07 14:19:11 +00:00
res [ data . id ] = t_data
2009-01-02 12:01:53 +00:00
if self . _type == ' many2one ' :
ids = filter ( None , res . values ( ) )
if ids :
2011-06-19 17:11:02 +00:00
# name_get as root, as seeing the name of a related
# object depends on access right of source document,
# not target, so user may not have access.
2010-06-12 22:35:30 +00:00
ng = dict ( obj . pool . get ( self . _obj ) . name_get ( cr , 1 , ids , context = context ) )
2009-01-02 12:01:53 +00:00
for r in res :
if res [ r ] :
res [ r ] = ( res [ r ] , ng [ res [ r ] ] )
2009-12-01 13:19:08 +00:00
elif self . _type in ( ' one2many ' , ' many2many ' ) :
for r in res :
if res [ r ] :
res [ r ] = [ x . id for x in res [ r ] ]
2008-09-18 11:28:57 +00:00
return res
2008-09-19 06:30:20 +00:00
2008-10-07 14:19:11 +00:00
def __init__ ( self , * arg , * * args ) :
2008-09-19 06:30:20 +00:00
self . arg = arg
2008-12-09 08:03:55 +00:00
self . _relations = [ ]
2011-07-01 23:23:28 +00:00
super ( related , self ) . __init__ ( self . _fnct_read , arg , self . _fnct_write , fnct_inv_arg = arg , fnct_search = self . _fnct_search , * * args )
2009-12-09 09:52:19 +00:00
if self . store is True :
# TODO: improve here to change self.store = {...} according to related objects
pass
2008-09-18 11:28:57 +00:00
2010-07-22 13:49:48 +00:00
def _field_get2 ( self , cr , uid , obj , context = None ) :
2008-12-09 08:03:55 +00:00
if self . _relations :
return
2011-11-18 13:11:04 +00:00
result = [ ]
2008-12-09 08:03:55 +00:00
obj_name = obj . _name
for i in range ( len ( self . _arg ) ) :
f = obj . pool . get ( obj_name ) . fields_get ( cr , uid , [ self . _arg [ i ] ] , context = context ) [ self . _arg [ i ] ]
2011-11-18 13:11:04 +00:00
result . append ( {
2008-12-09 08:03:55 +00:00
' object ' : obj_name ,
' type ' : f [ ' type ' ]
} )
if f . get ( ' relation ' , False ) :
obj_name = f [ ' relation ' ]
2011-11-18 13:11:04 +00:00
result [ - 1 ] [ ' relation ' ] = f [ ' relation ' ]
self . _relations = result
2008-08-12 14:44:56 +00:00
2011-09-17 10:05:38 +00:00
class sparse ( function ) :
def convert_value ( self , obj , cr , uid , record , value , read_value , context = None ) :
"""
+ For a many2many field , a list of tuples is expected .
Here is the list of tuple that are accepted , with the corresponding semantics : :
( 0 , 0 , { values } ) link to a new record that needs to be created with the given values dictionary
( 1 , ID , { values } ) update the linked record with id = ID ( write * values * on it )
( 2 , ID ) remove and delete the linked record with id = ID ( calls unlink on ID , that will delete the object completely , and the link to it as well )
( 3 , ID ) cut the link to the linked record with id = ID ( delete the relationship between the two objects but does not delete the target object itself )
( 4 , ID ) link to existing record with id = ID ( adds a relationship )
( 5 ) unlink all ( like using ( 3 , ID ) for all linked records )
( 6 , 0 , [ IDs ] ) replace the list of linked IDs ( like using ( 5 ) then ( 4 , ID ) for each ID in the list of IDs )
Example :
[ ( 6 , 0 , [ 8 , 5 , 6 , 4 ] ) ] sets the many2many to ids [ 8 , 5 , 6 , 4 ]
+ For a one2many field , a lits of tuples is expected .
Here is the list of tuple that are accepted , with the corresponding semantics : :
( 0 , 0 , { values } ) link to a new record that needs to be created with the given values dictionary
( 1 , ID , { values } ) update the linked record with id = ID ( write * values * on it )
( 2 , ID ) remove and delete the linked record with id = ID ( calls unlink on ID , that will delete the object completely , and the link to it as well )
Example :
[ ( 0 , 0 , { ' field_name ' : field_value_record1 , . . . } ) , ( 0 , 0 , { ' field_name ' : field_value_record2 , . . . } ) ]
"""
if self . _type == ' many2many ' :
2011-11-17 14:30:10 +00:00
assert value [ 0 ] [ 0 ] == 6 , ' Unsupported m2m value for sparse field: %s ' % value
return value [ 0 ] [ 2 ]
2011-09-17 10:05:38 +00:00
elif self . _type == ' one2many ' :
if not read_value :
2011-11-17 14:30:10 +00:00
read_value = [ ]
2011-09-17 10:05:38 +00:00
relation_obj = obj . pool . get ( self . relation )
for vals in value :
2011-11-17 14:30:10 +00:00
assert vals [ 0 ] in ( 0 , 1 , 2 ) , ' Unsupported o2m value for sparse field: %s ' % vals
2011-09-17 10:05:38 +00:00
if vals [ 0 ] == 0 :
read_value . append ( relation_obj . create ( cr , uid , vals [ 2 ] , context = context ) )
elif vals [ 0 ] == 1 :
relation_obj . write ( cr , uid , vals [ 1 ] , vals [ 2 ] , context = context )
elif vals [ 0 ] == 2 :
2011-11-17 14:30:10 +00:00
relation_obj . unlink ( cr , uid , vals [ 1 ] , context = context )
2011-09-17 10:05:38 +00:00
read_value . remove ( vals [ 1 ] )
2011-11-17 14:30:10 +00:00
return read_value
2011-09-17 10:05:38 +00:00
return value
def _fnct_write ( self , obj , cr , uid , ids , field_name , value , args , context = None ) :
if not type ( ids ) == list :
2011-11-01 15:16:39 +00:00
ids = [ ids ]
2011-09-17 10:05:38 +00:00
records = obj . browse ( cr , uid , ids , context = context )
for record in records :
# grab serialized value as object - already deserialized
2011-11-17 14:30:10 +00:00
serialized = getattr ( record , self . serialization_field )
2011-11-01 15:16:39 +00:00
if value is None :
2011-11-17 14:30:10 +00:00
# simply delete the key to unset it.
serialized . pop ( field_name , None )
2011-11-01 15:16:39 +00:00
else :
serialized [ field_name ] = self . convert_value ( obj , cr , uid , record , value , serialized . get ( field_name ) , context = context )
2011-09-17 10:05:38 +00:00
obj . write ( cr , uid , ids , { self . serialization_field : serialized } , context = context )
return True
def _fnct_read ( self , obj , cr , uid , ids , field_names , args , context = None ) :
2011-11-17 14:30:10 +00:00
results = { }
2011-09-17 10:05:38 +00:00
records = obj . browse ( cr , uid , ids , context = context )
for record in records :
# grab serialized value as object - already deserialized
2011-11-17 14:30:10 +00:00
serialized = getattr ( record , self . serialization_field )
results [ record . id ] = { }
2011-09-17 10:05:38 +00:00
for field_name in field_names :
2011-12-22 18:24:05 +00:00
field_type = obj . _columns [ field_name ] . _type
value = serialized . get ( field_name , False )
if field_type in ( ' one2many ' , ' many2many ' ) :
value = value or [ ]
if value :
# filter out deleted records as superuser
2012-01-05 10:40:51 +00:00
relation_obj = obj . pool . get ( obj . _columns [ field_name ] . relation )
2011-12-22 18:24:05 +00:00
value = relation_obj . exists ( cr , openerp . SUPERUSER_ID , value )
if type ( value ) in ( int , long ) and field_type == ' many2one ' :
2012-01-05 10:40:51 +00:00
relation_obj = obj . pool . get ( obj . _columns [ field_name ] . relation )
2011-12-22 18:24:05 +00:00
# check for deleted record as superuser
if not relation_obj . exists ( cr , openerp . SUPERUSER_ID , [ value ] ) :
value = False
results [ record . id ] [ field_name ] = value
2011-09-17 10:05:38 +00:00
return results
def __init__ ( self , serialization_field , * * kwargs ) :
self . serialization_field = serialization_field
2012-01-04 13:30:27 +00:00
return super ( sparse , self ) . __init__ ( self . _fnct_read , fnct_inv = self . _fnct_write , multi = ' __sparse_multi ' , * * kwargs )
2011-09-17 10:05:38 +00:00
2009-09-17 07:27:12 +00:00
# ---------------------------------------------------------
# Dummy fields
# ---------------------------------------------------------
class dummy ( function ) :
2010-07-22 13:49:48 +00:00
def _fnct_search ( self , tobj , cr , uid , obj = None , name = None , domain = None , context = None ) :
2009-09-17 07:27:12 +00:00
return [ ]
2010-07-22 13:49:48 +00:00
def _fnct_write ( self , obj , cr , uid , ids , field_name , values , args , context = None ) :
2009-09-17 07:27:12 +00:00
return False
def _fnct_read ( self , obj , cr , uid , ids , field_name , args , context = None ) :
return { }
2010-03-08 09:40:36 +00:00
2009-09-17 07:27:12 +00:00
def __init__ ( self , * arg , * * args ) :
self . arg = arg
self . _relations = [ ]
2011-07-01 23:23:28 +00:00
super ( dummy , self ) . __init__ ( self . _fnct_read , arg , self . _fnct_write , fnct_inv_arg = arg , fnct_search = None , * * args )
2009-09-17 07:27:12 +00:00
2006-12-07 13:41:40 +00:00
# ---------------------------------------------------------
# Serialized fields
# ---------------------------------------------------------
2011-09-17 10:05:38 +00:00
2006-12-07 13:41:40 +00:00
class serialized ( _column ) :
2011-09-04 23:43:04 +00:00
""" A field able to store an arbitrary python data structure.
Note : only plain components allowed .
"""
def _symbol_set_struct ( val ) :
2012-01-18 12:33:34 +00:00
return simplejson . dumps ( val )
2011-09-04 23:43:04 +00:00
def _symbol_get_struct ( self , val ) :
2012-01-18 12:33:34 +00:00
return simplejson . loads ( val or ' {} ' )
2011-09-04 23:43:04 +00:00
_prefetch = False
_type = ' serialized '
_symbol_c = ' %s '
_symbol_f = _symbol_set_struct
_symbol_set = ( _symbol_c , _symbol_f )
_symbol_get = _symbol_get_struct
2006-12-07 13:41:40 +00:00
2010-09-18 10:19:58 +00:00
# TODO: review completly this class for speed improvement
2006-12-07 13:41:40 +00:00
class property ( function ) :
2007-08-30 14:22:03 +00:00
2010-05-04 14:46:42 +00:00
def _get_default ( self , obj , cr , uid , prop_name , context = None ) :
2011-07-07 14:03:59 +00:00
return self . _get_defaults ( obj , cr , uid , [ prop_name ] , context = None ) [ prop_name ]
2010-06-10 16:20:17 +00:00
2011-07-07 13:37:33 +00:00
def _get_defaults ( self , obj , cr , uid , prop_names , context = None ) :
2011-07-08 12:51:06 +00:00
""" Get the default values for ``prop_names´ ´ property fields (result of ir.property.get() function for res_id = False).
2010-06-10 16:20:17 +00:00
2011-07-08 12:51:06 +00:00
: param list of string prop_names : list of name of property fields for those we want the default value
: return : map of property field names to their default value
: rtype : dict
2011-07-07 13:37:33 +00:00
"""
2010-05-04 14:46:42 +00:00
prop = obj . pool . get ( ' ir.property ' )
2011-07-11 16:15:58 +00:00
res = { }
2011-07-07 13:37:33 +00:00
for prop_name in prop_names :
2011-07-11 16:15:58 +00:00
res [ prop_name ] = prop . get ( cr , uid , prop_name , obj . _name , context = context )
return res
2010-05-04 14:46:42 +00:00
def _get_by_id ( self , obj , cr , uid , prop_name , ids , context = None ) :
prop = obj . pool . get ( ' ir.property ' )
vids = [ obj . _name + ' , ' + str ( oid ) for oid in ids ]
2011-07-11 12:34:14 +00:00
domain = [ ( ' fields_id.model ' , ' = ' , obj . _name ) , ( ' fields_id.name ' , ' in ' , prop_name ) ]
2010-09-19 12:34:45 +00:00
#domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
2010-09-21 08:55:49 +00:00
if vids :
2010-05-05 10:02:41 +00:00
domain = [ ( ' res_id ' , ' in ' , vids ) ] + domain
2010-09-21 08:55:49 +00:00
return prop . search ( cr , uid , domain , context = context )
2010-05-04 14:46:42 +00:00
2010-09-18 14:35:59 +00:00
# TODO: to rewrite more clean
2010-05-04 14:46:42 +00:00
def _fnct_write ( self , obj , cr , uid , id , prop_name , id_val , obj_dest , context = None ) :
if context is None :
2008-08-12 14:44:56 +00:00
context = { }
2008-07-22 14:24:36 +00:00
2010-09-19 12:34:45 +00:00
nids = self . _get_by_id ( obj , cr , uid , [ prop_name ] , [ id ] , context )
2010-05-04 14:46:42 +00:00
if nids :
cr . execute ( ' DELETE FROM ir_property WHERE id IN %s ' , ( tuple ( nids ) , ) )
default_val = self . _get_default ( obj , cr , uid , prop_name , context )
2011-06-08 10:41:28 +00:00
property_create = False
2011-10-05 15:48:57 +00:00
if isinstance ( default_val , openerp . osv . orm . browse_record ) :
if default_val . id != id_val :
2011-06-08 10:41:28 +00:00
property_create = True
2011-10-05 15:48:57 +00:00
elif default_val != id_val :
2011-06-08 10:41:28 +00:00
property_create = True
if property_create :
2010-05-04 14:46:42 +00:00
def_id = self . _field_get ( cr , uid , obj . _name , prop_name )
company = obj . pool . get ( ' res.company ' )
cid = company . _company_default_get ( cr , uid , obj . _name , def_id ,
context = context )
propdef = obj . pool . get ( ' ir.model.fields ' ) . browse ( cr , uid , def_id ,
context = context )
prop = obj . pool . get ( ' ir.property ' )
return prop . create ( cr , uid , {
2008-07-22 14:24:36 +00:00
' name ' : propdef . name ,
2010-05-04 14:46:42 +00:00
' value ' : id_val ,
2008-07-22 14:24:36 +00:00
' res_id ' : obj . _name + ' , ' + str ( id ) ,
2010-05-04 14:46:42 +00:00
' company_id ' : cid ,
' fields_id ' : def_id ,
2010-05-05 10:02:41 +00:00
' type ' : self . _type ,
2010-09-19 12:34:45 +00:00
} , context = context )
2010-05-04 14:46:42 +00:00
return False
2008-07-22 14:24:36 +00:00
2011-07-07 13:37:33 +00:00
def _fnct_read ( self , obj , cr , uid , ids , prop_names , obj_dest , context = None ) :
2011-07-11 12:34:14 +00:00
prop = obj . pool . get ( ' ir.property ' )
2011-08-26 10:35:16 +00:00
# get the default values (for res_id = False) for the property fields
2011-07-07 13:37:33 +00:00
default_val = self . _get_defaults ( obj , cr , uid , prop_names , context )
2010-05-04 14:46:42 +00:00
2011-07-11 12:34:14 +00:00
# build the dictionary that will be returned
2008-07-22 14:24:36 +00:00
res = { }
for id in ids :
2010-09-18 11:07:31 +00:00
res [ id ] = default_val . copy ( )
2011-07-11 12:34:14 +00:00
for prop_name in prop_names :
property_field = obj . _all_columns . get ( prop_name ) . column
2011-07-11 16:15:58 +00:00
property_destination_obj = property_field . _obj if property_field . _type == ' many2one ' else False
# If the property field is a m2o field, we will append the id of the value to name_get_ids
2011-07-11 12:34:14 +00:00
# in order to make a name_get in batch for all the ids needed.
2011-07-11 16:15:58 +00:00
name_get_ids = { }
2010-09-19 12:34:45 +00:00
for id in ids :
2011-07-11 12:34:14 +00:00
# get the result of ir.property.get() for this res_id and save it in res if it's existing
2011-07-07 14:03:59 +00:00
obj_reference = obj . _name + ' , ' + str ( id )
2011-07-11 12:34:14 +00:00
value = prop . get ( cr , uid , prop_name , obj . _name , res_id = obj_reference , context = context )
2011-07-07 13:37:33 +00:00
if value :
2011-07-11 12:34:14 +00:00
res [ id ] [ prop_name ] = value
# Check existence as root (as seeing the name of a related
2011-07-08 12:51:06 +00:00
# object depends on access right of source document,
# not target, so user may not have access) in order to avoid
# pointing on an unexisting record.
2011-07-11 14:25:03 +00:00
if property_destination_obj :
if res [ id ] [ prop_name ] and obj . pool . get ( property_destination_obj ) . exists ( cr , 1 , res [ id ] [ prop_name ] . id ) :
2011-07-11 16:15:58 +00:00
name_get_ids [ id ] = res [ id ] [ prop_name ] . id
2011-07-11 12:34:14 +00:00
else :
res [ id ] [ prop_name ] = False
if property_destination_obj :
2011-07-08 12:51:06 +00:00
# name_get as root (as seeing the name of a related
# object depends on access right of source document,
# not target, so user may not have access.)
2011-07-11 16:15:58 +00:00
name_get_values = dict ( obj . pool . get ( property_destination_obj ) . name_get ( cr , 1 , name_get_ids . values ( ) , context = context ) )
2011-07-11 12:34:14 +00:00
# the property field is a m2o, we need to return a tuple with (id, name)
2011-07-11 16:15:58 +00:00
for k , v in name_get_ids . iteritems ( ) :
if res [ k ] [ prop_name ] :
res [ k ] [ prop_name ] = ( v , name_get_values . get ( v ) )
2008-07-22 14:24:36 +00:00
return res
def _field_get ( self , cr , uid , model_name , prop ) :
if not self . field_id . get ( cr . dbname ) :
cr . execute ( ' SELECT id \
FROM ir_model_fields \
WHERE name = % s AND model = % s ' , (prop, model_name))
res = cr . fetchone ( )
self . field_id [ cr . dbname ] = res and res [ 0 ]
return self . field_id [ cr . dbname ]
def __init__ ( self , obj_prop , * * args ) :
2010-05-05 10:02:41 +00:00
# TODO remove obj_prop parameter (use many2one type)
2008-07-22 14:24:36 +00:00
self . field_id = { }
function . __init__ ( self , self . _fnct_read , False , self . _fnct_write ,
2010-09-18 11:07:31 +00:00
obj_prop , multi = ' properties ' , * * args )
2008-07-22 14:24:36 +00:00
def restart ( self ) :
self . field_id = { }
2008-07-08 08:13:12 +00:00
2008-07-23 15:01:27 +00:00
2011-09-30 11:23:48 +00:00
def field_to_dict ( model , cr , user , field , context = None ) :
2011-06-17 07:26:08 +00:00
""" Return a dictionary representation of a field.
The string , help , and selection attributes ( if any ) are untranslated . This
representation is the one returned by fields_get ( ) ( fields_get ( ) will do
the translation ) .
"""
res = { ' type ' : field . _type }
# This additional attributes for M2M and function field is added
# because we need to display tooltip with this additional information
# when client is started in debug mode.
if isinstance ( field , function ) :
res [ ' function ' ] = field . _fnct and field . _fnct . func_name or False
res [ ' store ' ] = field . store
if isinstance ( field . store , dict ) :
res [ ' store ' ] = str ( field . store )
res [ ' fnct_search ' ] = field . _fnct_search and field . _fnct_search . func_name or False
res [ ' fnct_inv ' ] = field . _fnct_inv and field . _fnct_inv . func_name or False
res [ ' fnct_inv_arg ' ] = field . _fnct_inv_arg or False
res [ ' func_obj ' ] = field . _obj or False
if isinstance ( field , many2many ) :
2011-09-30 11:23:48 +00:00
( table , col1 , col2 ) = field . _sql_names ( model )
res [ ' related_columns ' ] = [ col1 , col2 ]
res [ ' third_table ' ] = table
2011-06-17 07:26:08 +00:00
for arg in ( ' string ' , ' readonly ' , ' states ' , ' size ' , ' required ' , ' group_operator ' ,
2012-05-18 14:36:25 +00:00
' change_default ' , ' translate ' , ' help ' , ' select ' , ' selectable ' , ' groups ' ) :
2011-06-17 07:26:08 +00:00
if getattr ( field , arg ) :
res [ arg ] = getattr ( field , arg )
for arg in ( ' digits ' , ' invisible ' , ' filters ' ) :
if getattr ( field , arg , None ) :
res [ arg ] = getattr ( field , arg )
if field . string :
res [ ' string ' ] = field . string
if field . help :
res [ ' help ' ] = field . help
if hasattr ( field , ' selection ' ) :
if isinstance ( field . selection , ( tuple , list ) ) :
res [ ' selection ' ] = field . selection
else :
# call the 'dynamic selection' function
2011-09-30 11:23:48 +00:00
res [ ' selection ' ] = field . selection ( model , cr , user , context )
2012-03-22 16:45:40 +00:00
if res [ ' type ' ] in ( ' one2many ' , ' many2many ' , ' many2one ' ) :
2011-06-17 07:26:08 +00:00
res [ ' relation ' ] = field . _obj
res [ ' domain ' ] = field . _domain
res [ ' context ' ] = field . _context
2011-11-09 15:56:45 +00:00
if isinstance ( field , one2many ) :
res [ ' relation_field ' ] = field . _fields_id
2011-06-17 07:26:08 +00:00
return res
2011-06-21 15:53:42 +00:00
class column_info ( object ) :
""" Struct containing details about an osv column, either one local to
its model , or one inherited via _inherits .
: attr name : name of the column
: attr column : column instance , subclass of osv . fields . _column
: attr parent_model : if the column is inherited , name of the model
that contains it , None for local columns .
: attr parent_column : the name of the column containing the m2o
relationship to the parent model that contains
this column , None for local columns .
2011-08-26 10:35:16 +00:00
: attr original_parent : if the column is inherited , name of the original
parent model that contains it i . e in case of multilevel
inheritence , None for local columns .
2011-06-21 15:53:42 +00:00
"""
2011-08-26 10:35:16 +00:00
def __init__ ( self , name , column , parent_model = None , parent_column = None , original_parent = None ) :
2011-06-21 15:53:42 +00:00
self . name = name
self . column = column
self . parent_model = parent_model
self . parent_column = parent_column
2011-08-26 10:35:16 +00:00
self . original_parent = original_parent
2011-06-21 15:53:42 +00:00
2008-07-23 15:01:27 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: