X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=openerp%2Fmodels.py;h=ac935c5c924d58631eda4933e2d82613fdfe3b21;hb=e17222a38d2d4cd32057a59e65be82edd103836c;hp=d44ced403a43af9bba535d9b8e2ea82e7e498f60;hpb=999ed04c40ec7a8465dc959b75cc6617cb4e6bf3;p=odoo%2Fodoo.git diff --git a/openerp/models.py b/openerp/models.py index d44ced4..ac935c5 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -241,6 +241,11 @@ class MetaModel(api.Meta): 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__: @@ -2269,7 +2274,7 @@ class BaseModel(object): _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 @@ -2281,19 +2286,26 @@ class BaseModel(object): 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): """ @@ -2687,7 +2699,7 @@ class BaseModel(object): """ 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 @@ -2800,9 +2812,14 @@ class BaseModel(object): 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, @@ -2826,14 +2843,15 @@ class BaseModel(object): # 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']]: @@ -2991,9 +3009,11 @@ class BaseModel(object): "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. @@ -3001,16 +3021,14 @@ class BaseModel(object): 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: @@ -3019,14 +3037,15 @@ class BaseModel(object): 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 @@ -3602,38 +3621,67 @@ class BaseModel(object): :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 @@ -4739,14 +4787,15 @@ class BaseModel(object): 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.")) @@ -5313,16 +5362,21 @@ class BaseModel(object): return record # - # Dirty flag, to mark records modified (in draft mode) + # Dirty flags, to mark record fields modified (in draft mode) # - @property - def _dirty(self): + def _is_dirty(self): """ Return whether any record in `self` is dirty. """ dirty = self.env.dirty - return any(bool(dirty.get(record)) for record in self) + return any(record in dirty for record in self) + + def _get_dirty(self): + """ Return the list of field names for which `self` is dirty. """ + dirty = self.env.dirty + return list(dirty.get(self, ())) - def _set_dirty_by(self, field_name): + def _set_dirty(self, field_name): + """ Mark the records in `self` as dirty for the given `field_name`. """ dirty = self.env.dirty for record in self: dirty[record].add(field_name) @@ -5727,7 +5781,7 @@ class BaseModel(object): field = self._fields[name] newval = record[name] if field.type in ('one2many', 'many2many'): - if newval != oldval or newval._dirty: + if newval != oldval or newval._is_dirty(): # put new value in result result['value'][name] = field.convert_to_write( newval, record._origin, subfields.get(name),