[MERGE]: Merge with latest trunk-server
[odoo/odoo.git] / openerp / osv / fields.py
index 6619e66..42bc9b7 100644 (file)
@@ -41,6 +41,7 @@ import warnings
 import xmlrpclib
 from psycopg2 import Binary
 
+import openerp
 import openerp.netsvc as netsvc
 import openerp.tools as tools
 from openerp.tools.translate import _
@@ -130,6 +131,12 @@ class boolean(_column):
     _symbol_f = lambda x: x and 'True' or 'False'
     _symbol_set = (_symbol_c, _symbol_f)
 
+    def __init__(self, string='unknown', required=False, **args):
+        super(boolean, self).__init__(string=string, required=required, **args)
+        if required:
+            warnings.warn("Making a boolean field `required` has no effect, as NULL values are "
+                          "automatically turned into False", PendingDeprecationWarning, stacklevel=2)
+
 class integer(_column):
     _type = 'integer'
     _symbol_c = '%s'
@@ -137,6 +144,12 @@ class integer(_column):
     _symbol_set = (_symbol_c, _symbol_f)
     _symbol_get = lambda self,x: x or 0
 
+    def __init__(self, string='unknown', required=False, **args):
+        super(integer, self).__init__(string=string, required=required, **args)
+        if required:
+            warnings.warn("Making an integer field `required` has no effect, as NULL values are "
+                          "automatically turned into 0", PendingDeprecationWarning, stacklevel=2)
+
 class integer_big(_column):
     """Experimental 64 bit integer column type, currently unused.
 
@@ -154,11 +167,29 @@ class integer_big(_column):
     _symbol_set = (_symbol_c, _symbol_f)
     _symbol_get = lambda self,x: x or 0
 
+    def __init__(self, string='unknown', required=False, **args):
+        super(integer_big, self).__init__(string=string, required=required, **args)
+        if required:
+            warnings.warn("Making an integer_big field `required` has no effect, as NULL values are "
+                          "automatically turned into 0", PendingDeprecationWarning, stacklevel=2)
+
 class reference(_column):
     _type = 'reference'
+    _classic_read = False # post-process to handle missing target
+
     def __init__(self, string, selection, size, **args):
         _column.__init__(self, string=string, size=size, selection=selection, **args)
 
+    def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
+        result = {}
+        # copy initial values fetched previously.
+        for value in values:
+            result[value['id']] = value[name]
+            if value[name]:
+                model, res_id = value[name].split(',')
+                if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
+                    result[value['id']] = False
+        return result
 
 class char(_column):
     _type = 'char'
@@ -195,10 +226,13 @@ class float(_column):
     _symbol_set = (_symbol_c, _symbol_f)
     _symbol_get = lambda self,x: x or 0.0
 
-    def __init__(self, string='unknown', digits=None, digits_compute=None, **args):
-        _column.__init__(self, string=string, **args)
+    def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
+        _column.__init__(self, string=string, required=required, **args)
         self.digits = digits
         self.digits_compute = digits_compute
+        if required:
+            warnings.warn("Making a float field `required` has no effect, as NULL values are "
+                          "automatically turned into 0.0", PendingDeprecationWarning, stacklevel=2)
 
 
     def digits_change(self, cr):
@@ -495,6 +529,15 @@ class one2many(_column):
 class many2many(_column):
     """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
        low-level details of the intermediary relationship table transparently.
+       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.
 
        :param str obj: destination model
        :param str rel: optional name of the intermediary relationship table. If not specified,
@@ -516,7 +559,7 @@ class many2many(_column):
     _type = 'many2many'
 
     def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
-        """ 
+        """
         """
         _column.__init__(self, string=string, **args)
         self._obj = obj
@@ -530,7 +573,7 @@ class many2many(_column):
 
     def _sql_names(self, source_model):
         """Return the SQL names defining the structure of the m2m relationship table
-            
+
             :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
@@ -972,10 +1015,10 @@ class function(_column):
                 result = (value, dict_names[value])
 
         if field_type == 'binary':
-            if context.get('bin_size', False):
+            if context.get('bin_size'):
                 # client requests only the size of binary fields
                 result = get_nice_size(value)
-            else:
+            elif not context.get('bin_raw'):
                 result = sanitize_binary_value(value)
 
         if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
@@ -1015,7 +1058,7 @@ class related(function):
 
        _columns = {
            'foo_id': fields.many2one('my.foo', 'Foo'),
-           'bar': fields.related('frol', 'foo_id', type='char', string='Frol of Foo'),
+           'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
         }
     """
 
@@ -1123,17 +1166,19 @@ class related(function):
     def _field_get2(self, cr, uid, obj, context=None):
         if self._relations:
             return
+        result = []
         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]]
-            self._relations.append({
+            result.append({
                 'object': obj_name,
                 'type': f['type']
 
             })
             if f.get('relation',False):
                 obj_name = f['relation']
-                self._relations[-1]['relation'] = f['relation']
+                result[-1]['relation'] = f['relation']
+        self._relations = result
 
 # ---------------------------------------------------------
 # Dummy fields
@@ -1206,7 +1251,14 @@ class property(function):
 
         default_val = self._get_default(obj, cr, uid, prop_name, context)
 
-        if id_val is not default_val:
+        property_create = False
+        if isinstance(default_val, openerp.osv.orm.browse_record):
+            if default_val.id != id_val:
+                property_create = True
+        elif default_val != id_val:
+            property_create = True
+
+        if property_create:
             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,
@@ -1285,7 +1337,7 @@ class property(function):
         self.field_id = {}
 
 
-def field_to_dict(self, cr, user, context, field):
+def field_to_dict(model, cr, user, field, context=None):
     """ Return a dictionary representation of a field.
 
     The string, help, and selection attributes (if any) are untranslated.  This
@@ -1308,8 +1360,9 @@ def field_to_dict(self, cr, user, context, field):
         res['fnct_inv_arg'] = field._fnct_inv_arg or False
         res['func_obj'] = field._obj or False
     if isinstance(field, many2many):
-        res['related_columns'] = list((field._id1, field._id2))
-        res['third_table'] = field._rel
+        (table, col1, col2) = field._sql_names(model)
+        res['related_columns'] = [col1, col2]
+        res['third_table'] = table
     for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
             'change_default', 'translate', 'help', 'select', 'selectable'):
         if getattr(field, arg):
@@ -1328,12 +1381,15 @@ def field_to_dict(self, cr, user, context, field):
             res['selection'] = field.selection
         else:
             # call the 'dynamic selection' function
-            res['selection'] = field.selection(self, cr, user, context)
+            res['selection'] = field.selection(model, cr, user, context)
     if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
         res['relation'] = field._obj
         res['domain'] = field._domain
         res['context'] = field._context
 
+    if isinstance(field, one2many):
+        res['relation_field'] = field._fields_id
+
     return res