Previously availability of meta-information (failing constraint, impacted table and column, …) in pg errors (e.g. constraint check failure) was limited and only available through formatted error messages, which could be localised (by PG itself) so not did getting that information require string extraction it was brittle in the face of localised instances. Postgres 9.3 adds this meta-information to error diagnostic data (`PQresultErrorField`), allowing easy programmatic access to it. Psycopg2 [added a Diagnostic object](http://initd.org/psycopg/docs/extensions.html#psycopg2.extensions.Diagnostics) around the time pg9.3 was itself released. Assuming Odoo now requires pg >= 9.3 we can remove the old string munging extraction for diagnostic. * funny story, the handling of 23505 didn't actually work because "duplicate key value violates unique constraint" is a literal part of the error message, I'd misunderstood "value" as a field name because my test model's field was called "value". Makes sense too since you can set UNIQUE constraints on multiple fields (and UNIQUE indices on expressions) * for both errors, it should be possible to use the table_name or constraint metadata to provide messages about sub-model issues (a constraint on an m2o), but that's not currently handled Fixes #15323
159 lines
4.9 KiB
Python
159 lines
4.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, fields, models
|
|
|
|
|
|
def selection_fn(model):
|
|
return [(str(key), val) for key, val in enumerate(["Corge", "Grault", "Wheee", "Moog"])]
|
|
|
|
def compute_fn(records):
|
|
for record in records:
|
|
record.value = 3
|
|
|
|
def inverse_fn(records):
|
|
pass
|
|
|
|
MODELS = [
|
|
('boolean', fields.Boolean()),
|
|
('integer', fields.Integer()),
|
|
('float', fields.Float()),
|
|
('decimal', fields.Float(digits=(16, 3))),
|
|
('string.bounded', fields.Char(size=16)),
|
|
('string.required', fields.Char(size=None, required=True)),
|
|
('string', fields.Char(size=None)),
|
|
('date', fields.Date()),
|
|
('datetime', fields.Datetime()),
|
|
('text', fields.Text()),
|
|
('selection', fields.Selection([(1, "Foo"), (2, "Bar"), (3, "Qux"), (4, '')])),
|
|
('selection.function', fields.Selection(selection_fn)),
|
|
# just relate to an integer
|
|
('many2one', fields.Many2one('export.integer')),
|
|
('one2many', fields.One2many('export.one2many.child', 'parent_id')),
|
|
('many2many', fields.Many2many('export.many2many.other')),
|
|
('function', fields.Integer(compute=compute_fn, inverse=inverse_fn)),
|
|
# related: specialization of fields.function, should work the same way
|
|
# TODO: reference
|
|
]
|
|
|
|
for name, field in MODELS:
|
|
class NewModel(models.Model):
|
|
_name = 'export.%s' % name
|
|
const = fields.Integer(default=4)
|
|
value = field
|
|
|
|
@api.multi
|
|
def name_get(self):
|
|
return [(record.id, "%s:%s" % (self._name, record.value)) for record in self]
|
|
|
|
@api.model
|
|
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
|
if isinstance(name, basestring) and name.split(':')[0] == self._name:
|
|
records = self.search([('value', operator, int(name.split(':')[1]))])
|
|
return records.name_get()
|
|
else:
|
|
return []
|
|
|
|
|
|
class One2ManyChild(models.Model):
|
|
_name = 'export.one2many.child'
|
|
# FIXME: orm.py:1161, fix to name_get on m2o field
|
|
_rec_name = 'value'
|
|
|
|
parent_id = fields.Many2one('export.one2many')
|
|
str = fields.Char()
|
|
value = fields.Integer()
|
|
|
|
@api.multi
|
|
def name_get(self):
|
|
return [(record.id, "%s:%s" % (self._name, record.value)) for record in self]
|
|
|
|
@api.model
|
|
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
|
if isinstance(name, basestring) and name.split(':')[0] == self._name:
|
|
records = self.search([('value', operator, int(name.split(':')[1]))])
|
|
return records.name_get()
|
|
else:
|
|
return []
|
|
|
|
|
|
class One2ManyMultiple(models.Model):
|
|
_name = 'export.one2many.multiple'
|
|
|
|
parent_id = fields.Many2one('export.one2many.recursive')
|
|
const = fields.Integer(default=36)
|
|
child1 = fields.One2many('export.one2many.child.1', 'parent_id')
|
|
child2 = fields.One2many('export.one2many.child.2', 'parent_id')
|
|
|
|
|
|
class One2ManyChildMultiple(models.Model):
|
|
_name = 'export.one2many.multiple.child'
|
|
# FIXME: orm.py:1161, fix to name_get on m2o field
|
|
_rec_name = 'value'
|
|
|
|
parent_id = fields.Many2one('export.one2many.multiple')
|
|
str = fields.Char()
|
|
value = fields.Integer()
|
|
|
|
@api.multi
|
|
def name_get(self):
|
|
return [(record.id, "%s:%s" % (self._name, record.value)) for record in self]
|
|
|
|
|
|
class One2ManyChild1(models.Model):
|
|
_name = 'export.one2many.child.1'
|
|
_inherit = 'export.one2many.multiple.child'
|
|
|
|
|
|
class One2ManyChild2(models.Model):
|
|
_name = 'export.one2many.child.2'
|
|
_inherit = 'export.one2many.multiple.child'
|
|
|
|
|
|
class Many2ManyChild(models.Model):
|
|
_name = 'export.many2many.other'
|
|
# FIXME: orm.py:1161, fix to name_get on m2o field
|
|
_rec_name = 'value'
|
|
|
|
str = fields.Char()
|
|
value = fields.Integer()
|
|
|
|
@api.multi
|
|
def name_get(self):
|
|
return [(record.id, "%s:%s" % (self._name, record.value)) for record in self]
|
|
|
|
@api.model
|
|
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
|
if isinstance(name, basestring) and name.split(':')[0] == self._name:
|
|
records = self.search([('value', operator, int(name.split(':')[1]))])
|
|
return records.name_get()
|
|
else:
|
|
return []
|
|
|
|
|
|
class SelectionWithDefault(models.Model):
|
|
_name = 'export.selection.withdefault'
|
|
|
|
const = fields.Integer(default=4)
|
|
value = fields.Selection([(1, "Foo"), (2, "Bar")], default=2)
|
|
|
|
|
|
class RecO2M(models.Model):
|
|
_name = 'export.one2many.recursive'
|
|
|
|
value = fields.Integer()
|
|
child = fields.One2many('export.one2many.multiple', 'parent_id')
|
|
|
|
|
|
class OnlyOne(models.Model):
|
|
_name = 'export.unique'
|
|
|
|
value = fields.Integer()
|
|
value2 = fields.Integer()
|
|
value3 = fields.Integer()
|
|
|
|
_sql_constraints = [
|
|
('value_unique', 'unique (value)', "The value must be unique"),
|
|
('pair_unique', 'unique (value2, value3)', "The values must be unique"),
|
|
]
|