Add One2One field

This commit is contained in:
Cédric Krier
2010-12-03 18:01:46 +01:00
parent 9f3986e7e3
commit 0d1c43b6ed
10 changed files with 314 additions and 19 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add One2One field
* Add AUTOINCREMENT to sqlite primary key
Version 1.8.0 - 2010-11-01
+22
View File
@@ -497,6 +497,28 @@ Instance methods:
Return the target :class:`~trytond.model.Model`.
One2One
-------
.. class:: One2One(relation_name, origin, target, string[, datetime_field[, \**options]])
A one-to-one relation field.
.. warning::
It is on the relation_name :class:`~trytond.model.Model` that the
unicity of the couple (origin, target) must be checked.
..
.. attribute:: One2One.datetime_field
Same as :attr:`Many2One.datetime_field`
Instance methods:
.. method:: One2One.get_target(pool)
Return the target :class:`~trytond.model.Model`.
Function
--------
+1 -1
View File
@@ -145,7 +145,7 @@ class BrowseRecord(object):
model = self._model.pool.get(j.model_name)
elif hasattr(j, 'get_target'):
model = j.get_target(self._model.pool)
if model and j._type in ('many2one',):
if model and j._type in ('many2one', 'one2one'):
if (not data[i]
and not (isinstance(data[i], (int, long))
and not isinstance(data[i],
+1
View File
@@ -18,3 +18,4 @@ from one2many import *
from many2many import *
from function import *
from property import *
from one2one import *
+48
View File
@@ -0,0 +1,48 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.model.fields.many2many import Many2Many
from trytond.transaction import Transaction
class One2One(Many2Many):
'''
Define one2one field (``int``).
'''
_type = 'one2one'
def get(self, ids, model, name, values=None):
'''
Return target record.
:param ids: a list of ids
:param model: a string with the name of the model
:param name: a string with the name of the field
:param values: a dictionary with the read values
:return: a dictionary with ids as key and target id as value
'''
res = super(One2One, self).get(ids, model, name, values=values)
for i, vals in res.iteritems():
res[i] = vals[0] if vals else False
return res
def set(self, ids, model, name, value):
'''
Set the values.
:param ids: A list of ids
:param model: A string with the name of the model
:param name: A string with the name of the field
:param value: The id to link
'''
relation_obj = model.pool.get(self.relation_name)
relation_ids = relation_obj.search([
(self.origin, 'in', ids),
])
relation_obj.delete(relation_ids)
if value:
for record_id in ids:
relation_obj.create({
self.origin: record_id,
self.target: value,
})
+2 -1
View File
@@ -534,7 +534,7 @@ class Model(object):
field = self._columns[field_name]
else:
field = self._inherit_fields[field_name][2]
if field._type in ('many2one',):
if field._type in ('many2one', 'one2one'):
if not isinstance(field_value, (int, long)):
continue
obj = self.pool.get(field.model_name)
@@ -740,6 +740,7 @@ class Model(object):
'one2many',
'many2many',
'many2one',
'one2one',
):
if hasattr(self._columns[field], 'model_name'):
relation = copy.copy(self._columns[field].model_name)
+35 -13
View File
@@ -632,8 +632,11 @@ class ModelSQL(ModelStorage):
if field not in fields_related.keys():
continue
fields_related2values.setdefault(field, {})
if self._columns[field]._type == 'many2one':
obj = self.pool.get(self._columns[field].model_name)
if self._columns[field]._type in ('many2one', 'one2one'):
if hasattr(self._columns[field], 'model_name'):
obj = self.pool.get(self._columns[field].model_name)
else:
obj = self._columns[field].get_target(self.pool)
if hasattr(self._columns[field], 'datetime_field') \
and self._columns[field].datetime_field:
for record in res:
@@ -678,7 +681,7 @@ class ModelSQL(ModelStorage):
if field not in self._columns:
continue
for related in fields_related[field]:
if self._columns[field]._type == 'many2one':
if self._columns[field]._type in ('many2one', 'one2one'):
if record[field]:
record[field + '.' + related] = \
fields_related2values[field]\
@@ -1358,18 +1361,37 @@ class ModelSQL(ModelStorage):
field = table._columns.get(fargs[0], False)
if len(fargs) > 1:
if field._type == 'many2one':
target_obj = self.pool.get(field.model_name)
if hasattr(field, 'search'):
domain.extend([(fargs[0], 'in',
self.pool.get(field.model_name).search([
(fargs[1], domain[i][1], domain[i][2]),
], order=[]))])
domain.extend([(fargs[0], 'in', target_obj.search([
(fargs[1], domain[i][1], domain[i][2]),
], order=[]))])
domain.pop(i)
else:
domain[i] = (fargs[0], 'inselect',
self.pool.get(field.model_name).search([
(fargs[1], domain[i][1], domain[i][2]),
], order=[], query_string=True),
table)
domain[i] = (fargs[0], 'inselect', target_obj.search([
(fargs[1], domain[i][1], domain[i][2]),
], order=[], query_string=True), table)
i += 1
continue
elif field._type in ('one2one', 'many2many'):
if hasattr(field, 'model_name'):
target_obj = self.pool.get(field.model_name)
else:
target_obj = field.get_target(self.pool)
relation_obj = self.pool.get(field.relation_name)
if hasattr(field, 'search'):
domain.extend([(fargs[0], 'in', target_obj.search([
(fargs[1], domain[i][1], domain[i][2]),
], order=[]))])
domain.pop(i)
else:
query1, query2 = target_obj.search([
(fargs[1], domain[i][1], domain[i][2]),
], order=[], query_string=True)
query1 = ('SELECT "%s" FROM "%s" WHERE "%s" IN (%s)' %
(field.origin, relation_obj._table,
field.target, query1))
domain[i] = ('id', 'inselect', (query1, query2))
i += 1
continue
else:
@@ -1433,7 +1455,7 @@ class ModelSQL(ModelStorage):
domain[i] = ('id', 'in', ids3)
i += 1
elif field._type == 'many2many':
elif field._type in ('many2many', 'one2one'):
# XXX must find a solution for long id list
if hasattr(field, 'model_name'):
target_obj = self.pool.get(field.model_name)
+10 -4
View File
@@ -278,7 +278,7 @@ class ModelStorage(Model):
data[field_name] = default[field_name]
elif ftype == 'function':
del data[field_name]
elif ftype == 'many2one':
elif ftype in ('many2one', 'one2one'):
try:
data[field_name] = data[field_name] and \
data[field_name][0]
@@ -648,6 +648,9 @@ class ModelStorage(Model):
res = [('set', res)]
return res
def get_one2one(relation, value):
return ('set', get_many2one(relation, value))
def get_reference(value):
if not value:
return False
@@ -749,6 +752,8 @@ class ModelStorage(Model):
res = get_many2one(this_field_def['relation'], value)
elif field_type == 'many2many':
res = get_many2many(this_field_def['relation'], value)
elif field_type == 'one2one':
res = get_one2one(this_field_def['relation'], value)
elif field_type == 'reference':
res = get_reference(value)
else:
@@ -969,8 +974,9 @@ class ModelStorage(Model):
not field.setter:
continue
# validate domain
if field._type in ('many2one', 'many2many', 'one2many') \
and field.domain:
if (field._type in
('many2one', 'many2many', 'one2many', 'one2one')
and field.domain):
if field._type in ('many2one', 'one2many'):
relation_obj = self.pool.get(field.model_name)
else:
@@ -1095,7 +1101,7 @@ class ModelStorage(Model):
for field in defaults.keys():
fld_def = (field in self._columns) and self._columns[field] \
or self._inherit_fields[field][2]
if fld_def._type in ('many2one',):
if fld_def._type in ('many2one', 'one2one'):
if isinstance(defaults[field], (list, tuple)):
vals[field] = defaults[field][0]
else:
+69
View File
@@ -354,3 +354,72 @@ class DateTimeRequired(ModelSQL):
required=True)
DateTimeRequired()
class One2One(ModelSQL):
'One2One'
_name = 'test.one2one'
_description = __doc__
name = fields.Char('Name', required=True)
one2one = fields.One2One('test.one2one.relation', 'origin', 'target',
string='One2One', help='Test one2one', required=False)
One2One()
class One2OneTarget(ModelSQL):
'One2One Target'
_name = 'test.one2one.target'
name = fields.Char('Name', required=True)
One2OneTarget()
class One2OneRelation(ModelSQL):
'One2One Relation'
_name = 'test.one2one.relation'
_description = __doc__
origin = fields.Many2One('test.one2one', 'Origin')
target = fields.Many2One('test.one2one.target', 'Target')
def __init__(self):
super(One2OneRelation, self).__init__()
self._sql_constraints += [
('origin_unique', 'UNIQUE(origin)',
'Origin must be unique'),
('target_unique', 'UNIQUE(target)',
'Target must be unique'),
]
One2OneRelation()
class One2OneRequired(ModelSQL):
'One2One'
_name = 'test.one2one_required'
_description = __doc__
name = fields.Char('Name', required=True)
one2one = fields.One2One('test.one2one_required.relation', 'origin', 'target',
string='One2One', help='Test one2one', required=True)
One2OneRequired()
class One2OneRequiredRelation(ModelSQL):
'One2One Relation'
_name = 'test.one2one_required.relation'
_description = __doc__
origin = fields.Many2One('test.one2one_required', 'Origin')
target = fields.Many2One('test.one2one.target', 'Target')
def __init__(self):
super(One2OneRequiredRelation, self).__init__()
self._sql_constraints += [
('origin_unique', 'UNIQUE(origin)',
'Origin must be unique'),
('target_unique', 'UNIQUE(target)',
'Target must be unique'),
]
One2OneRequiredRelation()
+125
View File
@@ -60,6 +60,10 @@ class FieldsTestCase(unittest.TestCase):
self.datetime_default = POOL.get('test.datetime_default')
self.datetime_required = POOL.get('test.datetime_required')
self.one2one = POOL.get('test.one2one')
self.one2one_target = POOL.get('test.one2one.target')
self.one2one_required = POOL.get('test.one2one_required')
def test0010boolean(self):
'''
Test Boolean.
@@ -2072,6 +2076,127 @@ class FieldsTestCase(unittest.TestCase):
transaction.cursor.rollback()
def test0100one2one(self):
'''
Test One2One.
'''
with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
target1_id = self.one2one_target.create({
'name': 'target1',
})
one2one1_id = self.one2one.create({
'name': 'origin1',
'one2one': target1_id,
})
self.assert_(one2one1_id)
one2one1 = self.one2one.read(one2one1_id, ['one2one',
'one2one.name'])
self.assert_(one2one1['one2one'] == target1_id)
self.assert_(one2one1['one2one.name'] == 'target1')
one2one_ids = self.one2one.search([
('one2one', '=', 'target1'),
])
self.assert_(one2one_ids == [one2one1_id])
one2one_ids = self.one2one.search([
('one2one', '!=', 'target1'),
])
self.assert_(one2one_ids == [])
one2one_ids = self.one2one.search([
('one2one', 'in', [target1_id]),
])
self.assert_(one2one_ids == [one2one1_id])
one2one_ids = self.one2one.search([
('one2one', 'in', [0]),
])
self.assert_(one2one_ids == [])
one2one_ids = self.one2one.search([
('one2one', 'not in', [target1_id]),
])
self.assert_(one2one_ids == [])
one2one_ids = self.one2one.search([
('one2one', 'not in', [0]),
])
self.assert_(one2one_ids == [one2one1_id])
one2one_ids = self.one2one.search([
('one2one.name', '=', 'target1'),
])
self.assert_(one2one_ids == [one2one1_id])
one2one_ids = self.one2one.search([
('one2one.name', '!=', 'target1'),
])
self.assert_(one2one_ids == [])
one2one = self.one2one.browse(one2one1_id)
self.assert_(one2one.one2one.name == 'target1')
one2one2_id = self.one2one.create({
'name': 'origin2',
})
self.assert_(one2one2_id)
one2one2 = self.one2one.read(one2one2_id, ['one2one'])
self.assert_(one2one2['one2one'] == False)
one2one_ids = self.one2one.search([
('one2one', '=', False),
])
self.assert_(one2one_ids == [one2one2_id])
target2_id = self.one2one_target.create({
'name': 'target2',
})
self.one2one.write(one2one2_id, {
'one2one': target2_id,
})
target2_id = self.one2one_target.search([
('name', '=', 'target2'),
])[0]
one2one2 = self.one2one.read(one2one2_id, ['one2one'])
self.assert_(one2one2['one2one'] == target2_id)
self.one2one.write(one2one2_id, {
'one2one': False,
})
one2one2 = self.one2one.read(one2one2_id, ['one2one'])
self.assert_(one2one2['one2one'] == False)
one2one2 = self.one2one.browse(one2one2_id)
self.assert_(not one2one2.one2one)
self.failUnlessRaises(Exception, self.one2one.create, {
'name': 'one2one3',
'one2one': target1_id,
})
self.failUnlessRaises(Exception, self.one2one.write, one2one2_id, {
'one2one': target1_id,
})
self.failUnlessRaises(Exception, self.one2one_required.create, {
'name': 'one2one3',
})
target3_id = self.one2one_target.create({
'name': 'target3_id',
})
one2one3_id = self.one2one_required.create({
'name': 'one2one3',
'one2one': target3_id,
})
self.assert_(one2one3_id)
transaction.cursor.rollback()
def suite():
return unittest.TestLoader().loadTestsFromTestCase(FieldsTestCase)