}
_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'):
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):
"""
"""
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!" % \