[MERGE] sync with latest trunk
authorOlivier Dony <odo@openerp.com>
Thu, 22 Sep 2011 15:41:55 +0000 (17:41 +0200)
committerOlivier Dony <odo@openerp.com>
Thu, 22 Sep 2011 15:41:55 +0000 (17:41 +0200)
bzr revid: odo@openerp.com-20110922154155-jq811w768d9zfgim

1  2 
openerp/addons/base/ir/ir_model.py
openerp/addons/base/res/res_currency.py
openerp/modules/loading.py
openerp/modules/registry.py
openerp/osv/expression.py
openerp/osv/fields.py
openerp/osv/orm.py

Simple merge
@@@ -67,12 -67,31 +67,31 @@@ class res_currency(osv.osv)
      }
      _defaults = {
          'active': lambda *a: 1,
-         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'res.currency', context=c)
+         'position' : 'after',
      }
+     _sql_constraints = [
+         # this constraint does not cover all cases due to SQL NULL handling for company_id,
+         # so it is complemented with a unique index (see below). The constraint and index
+         # share the same prefix so that IntegrityError triggered by the index will be caught
+         # and reported to the user with the constraint's error message.
+         ('unique_name_company_id', 'unique (name, company_id)', 'The currency code must be unique per company!'),
+     ]
      _order = "name"
  
+     def init(self, cr):
+         # CONSTRAINT/UNIQUE INDEX on (name,company_id) 
+         # /!\ The unique constraint 'unique_name_company_id' is not sufficient, because SQL92
+         # only support field names in constraint definitions, and we need a function here:
+         # we need to special-case company_id to treat all NULL company_id as equal, otherwise
+         # we would allow duplicate "global" currencies (all having company_id == NULL) 
+         cr.execute("""SELECT indexname FROM pg_indexes WHERE indexname = 'res_currency_unique_name_company_id_idx'""")
+         if not cr.fetchone():
+             cr.execute("""CREATE UNIQUE INDEX res_currency_unique_name_company_id_idx
+                           ON res_currency
+                           (name, (COALESCE(company_id,-1)))""")
      def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
 -        res = super(osv.osv, self).read(cr, user, ids, fields, context, load)
 +        res = super(res_currency, self).read(cr, user, ids, fields, context, load)
          currency_rate_obj = self.pool.get('res.currency.rate')
          for r in res:
              if r.__contains__('rate_ids'):
@@@ -347,8 -347,8 +347,8 @@@ def load_modules(db, force_demo=False, 
              cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
              for (model, name) in cr.fetchall():
                  model_obj = pool.get(model)
 -                if isinstance(model_obj, osv.osv.osv_memory) and not isinstance(model_obj, osv.osv.osv):
 -                    logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
 +                if model_obj.is_transient():
-                     logger.notifyChannel('init', netsvc.LOG_WARNING, 'The transient model %s (%s) should not have explicit access rules!' % (model, name))
++                    logger.notifyChannel('init', netsvc.LOG_WARNING, 'The transient model (formerly osv_memory) %s (%s) should not have explicit access rules!' % (model, name))
  
              cr.execute("SELECT model from ir_model")
              for (model,) in cr.fetchall():
Simple merge
@@@ -88,16 -218,126 +218,133 @@@ def AND(domains)
      return combine(AND_OPERATOR, TRUE_DOMAIN, FALSE_DOMAIN, domains)
  
  def OR(domains):
-     """ OR([D1,D2,...]) returns a domain representing D1 or D2 or ... """
+     """OR([D1,D2,...]) returns a domain representing D1 or D2 or ... """
      return combine(OR_OPERATOR, FALSE_DOMAIN, TRUE_DOMAIN, domains)
  
 +# Probably bad style: the domain should be already normalized (and non-empty).
 +def expression_and(term, domain):
 +    if domain:
 +        return ['&', term] + normalize(domain)
 +    else:
 +        return term
 +
+ def is_operator(element):
+     """Test whether an object is a valid domain operator. """
+     return isinstance(element, basestring) and element in DOMAIN_OPERATORS
+ # TODO change the share wizard to use this function.
+ def is_leaf(element, internal=False):
+     """ Test whether an object is a valid domain term.
+     :param internal: allow or not the 'inselect' internal operator in the term.
+     This normally should be always left to False.
+     """
+     INTERNAL_OPS = TERM_OPERATORS + ('inselect',)
+     return (isinstance(element, tuple) or isinstance(element, list)) \
+        and len(element) == 3 \
+        and (((not internal) and element[1] in TERM_OPERATORS + ('<>',)) \
+             or (internal and element[1] in INTERNAL_OPS + ('<>',)))
+ def normalize_leaf(left, operator, right):
+     """ Change a term's operator to some canonical form, simplifying later
+     processing.
+     """
+     original = operator
+     operator = operator.lower()
+     if operator == '<>':
+         operator = '!='
+     if isinstance(right, bool) and operator in ('in', 'not in'):
+         _logger.warning("The domain term '%s' should use the '=' or '!=' operator." % ((left, original, right),))
+         operator = '=' if operator == 'in' else '!='
+     if isinstance(right, (list, tuple)) and operator in ('=', '!='):
+         _logger.warning("The domain term '%s' should use the 'in' or 'not in' operator." % ((left, original, right),))
+         operator = 'in' if operator == '=' else 'not in'
+     return left, operator, right
+ def distribute_not(domain):
+     """ Distribute any '!' domain operators found inside a normalized domain.
+     Because we don't use SQL semantic for processing a 'left not in right'
+     query (i.e. our 'not in' is not simply translated to a SQL 'not in'),
+     it means that a '! left in right' can not be simply processed
+     by __leaf_to_sql by first emitting code for 'left in right' then wrapping
+     the result with 'not (...)', as it would result in a 'not in' at the SQL
+     level.
+     This function is thus responsible for pushing any '!' domain operators
+     inside the terms themselves. For example::
+          ['!','&',('user_id','=',4),('partner_id','in',[1,2])]
+             will be turned into:
+          ['|',('user_id','!=',4),('partner_id','not in',[1,2])]
+     """
+     def negate(leaf):
+         """Negates and returns a single domain leaf term,
+         using the opposite operator if possible"""
+         left, operator, right = leaf
+         mapping = {
+             '<': '>=',
+             '>': '<=',
+             '<=': '>',
+             '>=': '<',
+             '=': '!=',
+             '!=': '=',
+         }
+         if operator in ('in', 'like', 'ilike'):
+             operator = 'not ' + operator
+             return [(left, operator, right)]
+         if operator in ('not in', 'not like', 'not ilike'):
+             operator = operator[4:]
+             return [(left, operator, right)]
+         if operator in mapping:
+             operator = mapping[operator]
+             return [(left, operator, right)]
+         return [NOT_OPERATOR, (left, operator, right)]
+     def distribute_negate(domain):
+         """Negate the domain ``subtree`` rooted at domain[0],
+         leaving the rest of the domain intact, and return
+         (negated_subtree, untouched_domain_rest)
+         """
+         if is_leaf(domain[0]):
+             return negate(domain[0]), domain[1:]
+         if domain[0] == AND_OPERATOR:
+             done1, todo1 = distribute_negate(domain[1:])
+             done2, todo2 = distribute_negate(todo1)
+             return [OR_OPERATOR] + done1 + done2, todo2
+         if domain[0] == OR_OPERATOR:
+             done1, todo1 = distribute_negate(domain[1:])
+             done2, todo2 = distribute_negate(todo1)
+             return [AND_OPERATOR] + done1 + done2, todo2
+     if not domain:
+         return []
+     if domain[0] != NOT_OPERATOR:
+         return [domain[0]] + distribute_not(domain[1:])
+     if domain[0] == NOT_OPERATOR:
+         done, todo = distribute_negate(domain[1:])
+         return done + distribute_not(todo)
+ def select_from_where(cr, select_field, from_table, where_field, where_ids, where_operator):
+     # todo: merge into parent query as sub-query
+     res = []
+     if where_ids:
+         if where_operator in ['<','>','>=','<=']:
+             cr.execute('SELECT "%s" FROM "%s" WHERE "%s" %s %%s' % \
+                 (select_field, from_table, where_field, where_operator),
+                 (where_ids[0],)) # TODO shouldn't this be min/max(where_ids) ?
+             res = [r[0] for r in cr.fetchall()]
+         else: # TODO where_operator is supposed to be 'in'? It is called with child_of...
+             for i in range(0, len(where_ids), cr.IN_MAX):
+                 subids = where_ids[i:i+cr.IN_MAX]
+                 cr.execute('SELECT "%s" FROM "%s" WHERE "%s" IN %%s' % \
+                     (select_field, from_table, where_field), (tuple(subids),))
+                 res.extend([r[0] for r in cr.fetchall()])
+     return res
+ def select_distinct_from_where_not_null(cr, select_field, from_table):
+     cr.execute('SELECT distinct("%s") FROM "%s" where "%s" is not null' % \
+                (select_field, from_table, select_field))
+     return [r[0] for r in cr.fetchall()]
  
  class expression(object):
      """
Simple merge
@@@ -2072,20 -2111,27 +2099,16 @@@ class Model(object)
          """
          return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
  
 -    def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
 -        """
 -        Private implementation of search() method, allowing specifying the uid to use for the access right check.
 -        This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
 -        by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
 -
 -        :param access_rights_uid: optional user ID to use when checking access rights
 -                                  (not for ir.rules, this is only for ir.model.access)
 -        """
 -        raise NotImplementedError(_('The search method is not implemented on this object !'))
 -
      def name_get(self, cr, user, ids, context=None):
+         """Returns the preferred display value (text representation) for the records with the
+            given ``ids``. By default this will be the value of the ``name`` column, unless
+            the model implements a custom behavior.
+            Can sometimes be seen as the inverse function of :meth:`~.name_search`, but it is not
+            guaranteed to be.
+            :rtype: list(tuple)
+            :return: list of pairs ``(id,text_repr)`` for all records with the given ``ids``.
          """
-         :param cr: database cursor
-         :param user: current user id
-         :type user: integer
-         :param ids: list of ids
-         :param context: context arguments, like lang, time zone
-         :type context: dictionary
-         :return: tuples with the text representation of requested objects for to-many relationships
-         """
-         if not context:
-             context = {}
          if not ids:
              return []
          if isinstance(ids, (int, long)):
          return new_id
  
      def exists(self, cr, uid, ids, context=None):
++        """Checks whether the given id or ids exist in this model,
++           and return the list of ids that do. This is simple to use for
++           a truth test on a browse_record::
++
++               if record.exists():
++                   pass
++
++           :param ids: id or list of ids to check for existence
++           :type ids: int or [int]
++           :return: the list of ids that currently exist, out of
++                    the given `ids`
++        """
          if type(ids) in (int, long):
              ids = [ids]
-         query = 'SELECT count(1) FROM "%s"' % (self._table)
+         query = 'SELECT id FROM "%s"' % (self._table)
          cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
-         return cr.fetchone()[0] == len(ids)
+         return [x[0] for x in cr.fetchall()]
  
      def check_recursion(self, cr, uid, ids, context=None, parent=None):
          warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \