if not self._custom:
self.module_to_models.setdefault(self._module, []).append(self)
+ # check for new-api conversion error: leave comma after field definition
+ for key, val in attrs.iteritems():
+ if type(val) is tuple and len(val) == 1 and isinstance(val[0], Field):
+ _logger.error("Trailing comma after field definition: %s.%s", self, key)
+
# transform columns into new-style fields (enables field inheritance)
for name, column in self._columns.iteritems():
if name in self.__dict__:
_schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
self._table, column['attname'])
- def _save_constraint(self, cr, constraint_name, type):
+ def _save_constraint(self, cr, constraint_name, type, definition):
"""
Record the creation of a constraint for this model, to make it possible
to delete it later when the module is uninstalled. Type can be either
return
assert type in ('f', 'u')
cr.execute("""
- SELECT 1 FROM ir_model_constraint, ir_module_module
+ SELECT type, definition FROM ir_model_constraint, ir_module_module
WHERE ir_model_constraint.module=ir_module_module.id
AND ir_model_constraint.name=%s
AND ir_module_module.name=%s
""", (constraint_name, self._module))
- if not cr.rowcount:
+ constraints = cr.dictfetchone()
+ if not constraints:
cr.execute("""
INSERT INTO ir_model_constraint
- (name, date_init, date_update, module, model, type)
+ (name, date_init, date_update, module, model, type, definition)
VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC',
(SELECT id FROM ir_module_module WHERE name=%s),
- (SELECT id FROM ir_model WHERE model=%s), %s)""",
- (constraint_name, self._module, self._name, type))
+ (SELECT id FROM ir_model WHERE model=%s), %s, %s)""",
+ (constraint_name, self._module, self._name, type, definition))
+ elif constraints['type'] != type or (definition and constraints['definition'] != definition):
+ cr.execute("""
+ UPDATE ir_model_constraint
+ SET date_update=now() AT TIME ZONE 'UTC', type=%s, definition=%s
+ WHERE name=%s AND module = (SELECT id FROM ir_module_module WHERE name=%s)""",
+ (type, definition, constraint_name, self._module))
def _save_relation_table(self, cr, relation_table):
"""
""" Create the foreign keys recorded by _auto_init. """
for t, k, r, d in self._foreign_keys:
cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d))
- self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f')
+ self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f', False)
cr.commit()
del self._foreign_keys
for (key, con, _) in self._sql_constraints:
conname = '%s_%s' % (self._table, key)
- self._save_constraint(cr, conname, 'u')
- cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
- existing_constraints = cr.dictfetchall()
+ # using 1 to get result if no imc but one pgc
+ cr.execute("""SELECT definition, 1
+ FROM ir_model_constraint imc
+ RIGHT JOIN pg_constraint pgc
+ ON (pgc.conname = imc.name)
+ WHERE pgc.conname=%s
+ """, (conname, ))
+ existing_constraints = cr.dictfetchone()
sql_actions = {
'drop': {
'execute': False,
# constraint does not exists:
sql_actions['add']['execute'] = True
sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
- elif unify_cons_text(con) not in [unify_cons_text(item['condef']) for item in existing_constraints]:
+ elif unify_cons_text(con) != existing_constraints['definition']:
# constraint exists but its definition has changed:
sql_actions['drop']['execute'] = True
- sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
+ sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints['definition'] or '', )
sql_actions['add']['execute'] = True
sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
# we need to add the constraint:
+ self._save_constraint(cr, conname, 'u', unify_cons_text(con))
sql_actions = [item for item in sql_actions.values()]
sql_actions.sort(key=lambda x: x['order'])
for sql_action in [action for action in sql_actions if action['execute']]:
"Invalid rec_name %s for model %s" % (cls._rec_name, cls._name)
elif 'name' in cls._fields:
cls._rec_name = 'name'
+ elif 'x_name' in cls._fields:
+ cls._rec_name = 'x_name'
- def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
- """ fields_get([fields])
+ def fields_get(self, cr, user, allfields=None, context=None, write_access=True, attributes=None):
+ """ fields_get([fields][, attributes])
Return the definition of each field.
dictionaries. The _inherits'd fields are included. The string, help,
and selection (if present) attributes are translated.
- :param cr: database cursor
- :param user: current user id
- :param allfields: list of fields
- :param context: context arguments, like lang, time zone
- :return: dictionary of field dictionaries, each one describing a field of the business object
- :raise AccessError: * if user has no create/write rights on the requested object
-
+ :param allfields: list of fields to document, all if empty or not provided
+ :param attributes: list of description attributes to return for each field, all if empty or not provided
"""
recs = self.browse(cr, user, [], context)
+ has_access = functools.partial(recs.check_access_rights, raise_exception=False)
+ readonly = not (has_access('write') or has_access('create'))
+
res = {}
for fname, field in self._fields.iteritems():
if allfields and fname not in allfields:
continue
if field.groups and not recs.user_has_groups(field.groups):
continue
- res[fname] = field.get_description(recs.env)
- # if user cannot create or modify records, make all fields readonly
- has_access = functools.partial(recs.check_access_rights, raise_exception=False)
- if not (has_access('write') or has_access('create')):
- for description in res.itervalues():
+ description = field.get_description(recs.env)
+ if readonly:
description['readonly'] = True
description['states'] = {}
+ if attributes:
+ description = {k: v for k, v in description.iteritems()
+ if k in attributes}
+ res[fname] = description
return res
:raise ValidateError: if user tries to enter invalid value for a field that is not in selection
:raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
- .. _openerp/models/relationals/format:
-
- .. note:: Relational fields use a special "commands" format to manipulate their values
-
- This format is a list of command triplets executed sequentially,
- possible command triplets are:
-
- ``(0, _, values: dict)``
- links to a new record created from the provided values
- ``(1, id, values: dict)``
- updates the already-linked record of id ``id`` with the
- provided ``values``
- ``(2, id, _)``
- unlinks and deletes the linked record of id ``id``
- ``(3, id, _)``
- unlinks the linked record of id ``id`` without deleting it
- ``(4, id, _)``
- links to an existing record of id ``id``
- ``(5, _, _)``
- unlinks all records in the relation, equivalent to using
- the command ``3`` on every linked record
- ``(6, _, ids)``
- replaces the existing list of linked records by the provoded
- ones, equivalent to using ``5`` then ``4`` for each id in
- ``ids``)
-
- (in command triplets, ``_`` values are ignored and can be
- anything, generally ``0`` or ``False``)
-
- Any command can be used on :class:`~openerp.fields.Many2many`,
- only ``0``, ``1`` and ``2`` can be used on
- :class:`~openerp.fields.One2many`.
+ * For numeric fields (:class:`~openerp.fields.Integer`,
+ :class:`~openerp.fields.Float`) the value should be of the
+ corresponding type
+ * For :class:`~openerp.fields.Boolean`, the value should be a
+ :class:`python:bool`
+ * For :class:`~openerp.fields.Selection`, the value should match the
+ selection values (generally :class:`python:str`, sometimes
+ :class:`python:int`)
+ * For :class:`~openerp.fields.Many2one`, the value should be the
+ database identifier of the record to set
+ * Other non-relational fields use a string for value
+
+ .. danger::
+
+ for historical and compatibility reasons,
+ :class:`~openerp.fields.Date` and
+ :class:`~openerp.fields.Datetime` fields use strings as values
+ (written and read) rather than :class:`~python:datetime.date` or
+ :class:`~python:datetime.datetime`. These date strings are
+ UTC-only and formatted according to
+ :const:`openerp.tools.misc.DEFAULT_SERVER_DATE_FORMAT` and
+ :const:`openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT`
+ * .. _openerp/models/relationals/format:
+
+ :class:`~openerp.fields.One2many` and
+ :class:`~openerp.fields.Many2many` use a special "commands" format to
+ manipulate the set of records stored in/associated with the field.
+
+ This format is a list of triplets executed sequentially, where each
+ triplet is a command to execute on the set of records. Not all
+ commands apply in all situations. Possible commands are:
+
+ ``(0, _, values)``
+ adds a new record created from the provided ``value`` dict.
+ ``(1, id, values)``
+ updates an existing record of id ``id`` with the values in
+ ``values``. Can not be used in :meth:`~.create`.
+ ``(2, id, _)``
+ removes the record of id ``id`` from the set, then deletes it
+ (from the database). Can not be used in :meth:`~.create`.
+ ``(3, id, _)``
+ removes the record of id ``id`` from the set, but does not
+ delete it. Can not be used on
+ :class:`~openerp.fields.One2many`. Can not be used in
+ :meth:`~.create`.
+ ``(4, id, _)``
+ adds an existing record of id ``id`` to the set. Can not be
+ used on :class:`~openerp.fields.One2many`.
+ ``(5, _, _)``
+ removes all records from the set, equivalent to using the
+ command ``3`` on every record explicitly. Can not be used on
+ :class:`~openerp.fields.One2many`. Can not be used in
+ :meth:`~.create`.
+ ``(6, _, ids)``
+ replaces all existing records in the set by the ``ids`` list,
+ equivalent to using the command ``5`` followed by a command
+ ``4`` for each ``id`` in ``ids``. Can not be used on
+ :class:`~openerp.fields.One2many`.
+
+ .. note:: Values marked as ``_`` in the list above are ignored and
+ can be anything, generally ``0`` or ``False``.
"""
if not self:
return True
By convention, new records are returned as existing.
"""
- ids = filter(None, self._ids) # ids to check in database
+ ids, new_ids = [], []
+ for i in self._ids:
+ (ids if isinstance(i, (int, long)) else new_ids).append(i)
if not ids:
return self
query = """SELECT id FROM "%s" WHERE id IN %%s""" % self._table
- self._cr.execute(query, (ids,))
- ids = ([r[0] for r in self._cr.fetchall()] + # ids in database
- [id for id in self._ids if not id]) # new ids
- existing = self.browse(ids)
+ self._cr.execute(query, [tuple(ids)])
+ ids = [r[0] for r in self._cr.fetchall()]
+ existing = self.browse(ids + new_ids)
if len(existing) < len(self):
# mark missing records in cache with a failed value
exc = MissingError(_("Record does not exist or has been deleted."))