[FIX] base/res_partner: small fix for backward compatibility
[odoo/odoo.git] / openerp / osv / fields.py
index 3a8448e..922fd5f 100644 (file)
 
 import base64
 import datetime as DT
+import logging
+import pytz
 import re
-import string
-import sys
-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 _
 from openerp.tools import float_round, float_repr
-import json
+import simplejson
+
+_logger = logging.getLogger(__name__)
 
 def _symbol_set(symb):
     if symb == None or symb == False:
@@ -139,8 +139,10 @@ class boolean(_column):
     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)
+            _logger.debug(
+                "required=True is deprecated: making a boolean field"
+                " `required` has no effect, as NULL values are "
+                "automatically turned into False.")
 
 class integer(_column):
     _type = 'integer'
@@ -152,8 +154,10 @@ class integer(_column):
     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)
+            _logger.debug(
+                "required=True is deprecated: making an integer field"
+                " `required` has no effect, as NULL values are "
+                "automatically turned into 0.")
 
 class integer_big(_column):
     """Experimental 64 bit integer column type, currently unused.
@@ -176,8 +180,10 @@ class integer_big(_column):
     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)
+            _logger.debug(
+                "required=True is deprecated: making an integer_big field"
+                " `required` has no effect, as NULL values are "
+                "automatically turned into 0.")
 
 class reference(_column):
     _type = 'reference'
@@ -238,8 +244,10 @@ class float(_column):
         # synopsis: digits_compute(cr) ->  (precision, scale)
         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)
+            _logger.debug(
+                "required=True is deprecated: making a float field"
+                " `required` has no effect, as NULL values are "
+                "automatically turned into 0.0.")
 
     def digits_change(self, cr):
         if self.digits_compute:
@@ -252,6 +260,7 @@ class float(_column):
 
 class date(_column):
     _type = 'date'
+
     @staticmethod
     def today(*args):
         """ Returns the current date in a format fit for being a
@@ -263,6 +272,38 @@ class date(_column):
         return DT.date.today().strftime(
             tools.DEFAULT_SERVER_DATE_FORMAT)
 
+    @staticmethod
+    def context_today(model, cr, uid, context=None, timestamp=None):
+        """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.
+
+           :param Model model: model (osv) for which the date value is being
+                               computed - technical field, currently ignored,
+                               automatically passed when used in _defaults.
+           :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
+                                      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 
+        """
+        today = timestamp or DT.datetime.now()
+        context_today = None
+        if context and context.get('tz'):
+            try:
+                utc = pytz.timezone('UTC')
+                context_tz = pytz.timezone(context['tz'])
+                utc_today = utc.localize(today, is_dst=False) # UTC = no DST
+                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)
+
 class datetime(_column):
     _type = 'datetime'
     @staticmethod
@@ -276,6 +317,36 @@ class datetime(_column):
         return DT.datetime.now().strftime(
             tools.DEFAULT_SERVER_DATETIME_FORMAT)
 
+    @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
+
 class time(_column):
     _type = 'time'
     _deprecated = True
@@ -355,7 +426,7 @@ class one2one(_column):
     _deprecated = True
 
     def __init__(self, obj, string='unknown', **args):
-        warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
+        _logger.warning("The one2one field is deprecated and doesn't work anymore.")
         _column.__init__(self, string=string, **args)
         self._obj = obj
 
@@ -620,8 +691,9 @@ class many2many(_column):
         for id in ids:
             res[id] = []
         if offset:
-            warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
-                      DeprecationWarning, stacklevel=2)
+            _logger.warning(
+                "Specifying offset at a many2many.get() is deprecated and may"
+                " produce unpredictable results.")
         obj = model.pool.get(self._obj)
         rel, id1, id2 = self._sql_names(model)
 
@@ -1083,17 +1155,17 @@ class related(function):
     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
         self._field_get2(cr, uid, obj, context)
         i = len(self._arg)-1
-        sarg = name
+        sarg = name if isinstance(name, (list, tuple)) else [name]
         while i>0:
-            if type(sarg) in [type([]), type( (1,) )]:
-                where = [(self._arg[i], 'in', sarg)]
-            else:
-                where = [(self._arg[i], '=', sarg)]
             if domain:
                 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
                 domain = []
+            else:
+                where = [(self._arg[i], 'in', sarg)]
             sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
             i -= 1
+        if domain:   # happens if len(self._arg) == 1
+            return map(lambda x: (self._arg[0],x[1], x[2]), domain)
         return [(self._arg[0], 'in', sarg)]
 
     def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
@@ -1278,10 +1350,10 @@ class sparse(function):
                     value = value or []
                     if value:
                         # filter out deleted records as superuser
-                        relation_obj = obj.pool.get(self.relation)
+                        relation_obj = obj.pool.get(obj._columns[field_name].relation)
                         value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
                 if type(value) in (int,long) and field_type == 'many2one':
-                    relation_obj = obj.pool.get(self.relation)
+                    relation_obj = obj.pool.get(obj._columns[field_name].relation)
                     # check for deleted record as superuser
                     if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
                         value = False
@@ -1324,10 +1396,10 @@ class serialized(_column):
     """
     
     def _symbol_set_struct(val):
-        return json.dumps(val)
+        return simplejson.dumps(val)
 
     def _symbol_get_struct(self, val):
-        return json.loads(val or '{}')
+        return simplejson.loads(val or '{}')
     
     _prefetch = False
     _type = 'serialized'