[FIX] res.partner: column "address" does not exists anymore.
[odoo/odoo.git] / openerp / tests / test_orm.py
1 from openerp import exceptions
2 from openerp.tools import mute_logger
3 import common
4
5 UID = common.ADMIN_USER_ID
6 DB = common.DB
7
8
9 class TestORM(common.TransactionCase):
10     """ test special behaviors of ORM CRUD functions
11     
12         TODO: use real Exceptions types instead of Exception """
13
14     def setUp(self):
15         super(TestORM, self).setUp()
16         cr, uid = self.cr, self.uid
17         self.partner = self.registry('res.partner')
18         self.users = self.registry('res.users')
19         self.p1 = self.partner.name_create(cr, uid, 'W')[0]
20         self.p2 = self.partner.name_create(cr, uid, 'Y')[0]
21         self.ir_rule = self.registry('ir.rule')
22
23         # sample unprivileged user
24         employee_gid = self.ref('base.group_user')
25         self.uid2 = self.users.create(cr, uid, {'name': 'test user', 'login': 'test', 'groups_id': [4,employee_gid]})
26
27     @mute_logger('openerp.osv.orm')
28     def testAccessDeletedRecords(self):
29         """ Verify that accessing deleted records works as expected """
30         cr, uid, uid2, p1, p2 = self.cr, self.uid, self.uid2, self.p1, self.p2
31         self.partner.unlink(cr, uid, [p1])
32
33         # read() is expected to skip deleted records because our API is not
34         # transactional for a sequence of search()->read() performed from the
35         # client-side... a concurrent deletion could therefore cause spurious
36         # exceptions even when simply opening a list view!
37         # /!\ Using unprileged user to detect former side effects of ir.rules!
38         self.assertEqual([{'id': p2, 'name': 'Y'}], self.partner.read(cr, uid2, [p1,p2], ['name']), "read() should skip deleted records")
39         self.assertEqual([], self.partner.read(cr, uid2, [p1], ['name']), "read() should skip deleted records")
40
41         # Deleting an already deleted record should be simply ignored
42         self.assertTrue(self.partner.unlink(cr, uid, [p1]), "Re-deleting should be a no-op")
43
44         # Updating an already deleted record should raise, even as admin
45         with self.assertRaises(Exception):
46             self.partner.write(cr, uid, [p1], {'name': 'foo'})
47
48     @mute_logger('openerp.osv.orm')
49     def testAccessFilteredRecords(self):
50         """ Verify that accessing filtered records works as expected for non-admin user """
51         cr, uid, uid2, p1, p2 = self.cr, self.uid, self.uid2, self.p1, self.p2
52         partner_model = self.registry('ir.model').search(cr, uid, [('model','=','res.partner')])[0]
53         self.ir_rule.create(cr, uid, {'name': 'Y is invisible',
54                                       'domain_force': [('id', '!=', p1)],
55                                       'model_id': partner_model})
56         # search as unprivileged user
57         partners = self.partner.search(cr, uid2, [])
58         self.assertFalse(p1 in partners, "W should not be visible...")
59         self.assertTrue(p2 in partners, "... but Y should be visible")
60
61         # read as unprivileged user
62         with self.assertRaises(Exception):
63             self.partner.read(cr, uid2, [p1], ['name'])
64         # write as unprivileged user
65         with self.assertRaises(Exception):
66             self.partner.write(cr, uid2, [p1], {'name': 'foo'})
67         # unlink as unprivileged user
68         with self.assertRaises(Exception):
69             self.partner.unlink(cr, uid2, [p1])
70
71         # Prepare mixed case 
72         self.partner.unlink(cr, uid, [p2])
73         # read mixed records: some deleted and some filtered
74         with self.assertRaises(Exception):
75             self.partner.read(cr, uid2, [p1,p2], ['name'])
76         # delete mixed records: some deleted and some filtered
77         with self.assertRaises(Exception):
78             self.partner.unlink(cr, uid2, [p1,p2])
79
80
81 class TestInherits(common.TransactionCase):
82     """ test the behavior of the orm for models that use _inherits;
83         specifically: res.users, that inherits from res.partner
84     """
85
86     def setUp(self):
87         super(TestInherits, self).setUp()
88         self.partner = self.registry('res.partner')
89         self.user = self.registry('res.users')
90
91     def test_create(self):
92         """ creating a user should automatically create a new partner """
93         partners_before = self.partner.search(self.cr, UID, [])
94         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
95         foo = self.user.browse(self.cr, UID, foo_id)
96
97         self.assertNotIn(foo.partner_id.id, partners_before)
98
99     def test_create_with_ancestor(self):
100         """ creating a user with a specific 'partner_id' should not create a new partner """
101         par_id = self.partner.create(self.cr, UID, {'name': 'Foo'})
102         partners_before = self.partner.search(self.cr, UID, [])
103         foo_id = self.user.create(self.cr, UID, {'partner_id': par_id, 'login': 'foo', 'password': 'foo'})
104         partners_after = self.partner.search(self.cr, UID, [])
105
106         self.assertEqual(set(partners_before), set(partners_after))
107
108         foo = self.user.browse(self.cr, UID, foo_id)
109         self.assertEqual(foo.name, 'Foo')
110         self.assertEqual(foo.partner_id.id, par_id)
111
112     @mute_logger('openerp.osv.orm')
113     def test_read(self):
114         """ inherited fields should be read without any indirection """
115         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
116         foo_values, = self.user.read(self.cr, UID, [foo_id])
117         partner_id = foo_values['partner_id'][0]
118         partner_values, = self.partner.read(self.cr, UID, [partner_id])
119         self.assertEqual(foo_values['name'], partner_values['name'])
120
121         foo = self.user.browse(self.cr, UID, foo_id)
122         self.assertEqual(foo.name, foo.partner_id.name)
123
124     @mute_logger('openerp.osv.orm')
125     def test_copy(self):
126         """ copying a user should automatically copy its partner, too """
127         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
128         foo_before, = self.user.read(self.cr, UID, [foo_id])
129         bar_id = self.user.copy(self.cr, UID, foo_id, {'login': 'bar', 'password': 'bar'})
130         foo_after, = self.user.read(self.cr, UID, [foo_id])
131
132         self.assertEqual(foo_before, foo_after)
133
134         foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
135         self.assertEqual(bar.login, 'bar')
136         self.assertNotEqual(foo.id, bar.id)
137         self.assertNotEqual(foo.partner_id.id, bar.partner_id.id)
138
139     @mute_logger('openerp.osv.orm')
140     def test_copy_with_ancestor(self):
141         """ copying a user with 'parent_id' in defaults should not duplicate the partner """
142         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
143         par_id = self.partner.create(self.cr, UID, {'name': 'Bar'})
144
145         foo_before, = self.user.read(self.cr, UID, [foo_id])
146         partners_before = self.partner.search(self.cr, UID, [])
147         bar_id = self.user.copy(self.cr, UID, foo_id, {'partner_id': par_id, 'login': 'bar'})
148         foo_after, = self.user.read(self.cr, UID, [foo_id])
149         partners_after = self.partner.search(self.cr, UID, [])
150
151         self.assertEqual(foo_before, foo_after)
152         self.assertEqual(set(partners_before), set(partners_after))
153
154         foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
155         self.assertNotEqual(foo.id, bar.id)
156         self.assertEqual(bar.partner_id.id, par_id)
157         self.assertEqual(bar.login, 'bar', "login is given from copy parameters")
158         self.assertEqual(bar.password, foo.password, "password is given from original record")
159         self.assertEqual(bar.name, 'Bar', "name is given from specific partner")
160
161
162
163 CREATE = lambda values: (0, False, values)
164 UPDATE = lambda id, values: (1, id, values)
165 DELETE = lambda id: (2, id, False)
166 FORGET = lambda id: (3, id, False)
167 LINK_TO = lambda id: (4, id, False)
168 DELETE_ALL = lambda: (5, False, False)
169 REPLACE_WITH = lambda ids: (6, False, ids)
170
171 def sorted_by_id(list_of_dicts):
172     "sort dictionaries by their 'id' field; useful for comparisons"
173     return sorted(list_of_dicts, key=lambda d: d.get('id'))
174
175 class TestO2MSerialization(common.TransactionCase):
176     """ test the orm method 'write' on one2many fields """
177
178     def setUp(self):
179         super(TestO2MSerialization, self).setUp()
180         self.partner = self.registry('res.partner')
181
182     def test_no_command(self):
183         " empty list of commands yields an empty list of records "
184         results = self.partner.resolve_2many_commands(
185             self.cr, UID, 'child_ids', [])
186
187         self.assertEqual(results, [])
188
189     def test_CREATE_commands(self):
190         " returns the VALUES dict as-is "
191         values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]
192         results = self.partner.resolve_2many_commands(
193             self.cr, UID, 'child_ids', map(CREATE, values))
194
195         self.assertEqual(results, values)
196
197     def test_LINK_TO_command(self):
198         " reads the records from the database, records are returned with their ids. "
199         ids = [
200             self.partner.create(self.cr, UID, {'name': 'foo'}),
201             self.partner.create(self.cr, UID, {'name': 'bar'}),
202             self.partner.create(self.cr, UID, {'name': 'baz'})
203         ]
204         commands = map(LINK_TO, ids)
205
206         results = self.partner.resolve_2many_commands(
207             self.cr, UID, 'child_ids', commands, ['name'])
208
209         self.assertEqual(sorted_by_id(results), sorted_by_id([
210             {'id': ids[0], 'name': 'foo'},
211             {'id': ids[1], 'name': 'bar'},
212             {'id': ids[2], 'name': 'baz'}
213         ]))
214
215     def test_bare_ids_command(self):
216         " same as the equivalent LINK_TO commands "
217         ids = [
218             self.partner.create(self.cr, UID, {'name': 'foo'}),
219             self.partner.create(self.cr, UID, {'name': 'bar'}),
220             self.partner.create(self.cr, UID, {'name': 'baz'})
221         ]
222
223         results = self.partner.resolve_2many_commands(
224             self.cr, UID, 'child_ids', ids, ['name'])
225
226         self.assertEqual(sorted_by_id(results), sorted_by_id([
227             {'id': ids[0], 'name': 'foo'},
228             {'id': ids[1], 'name': 'bar'},
229             {'id': ids[2], 'name': 'baz'}
230         ]))
231
232     def test_UPDATE_command(self):
233         " take the in-db records and merge the provided information in "
234         id_foo = self.partner.create(self.cr, UID, {'name': 'foo'})
235         id_bar = self.partner.create(self.cr, UID, {'name': 'bar'})
236         id_baz = self.partner.create(self.cr, UID, {'name': 'baz', 'city': 'tag'})
237
238         results = self.partner.resolve_2many_commands(
239             self.cr, UID, 'child_ids', [
240                 LINK_TO(id_foo),
241                 UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}),
242                 UPDATE(id_baz, {'name': 'quux'})
243             ], ['name', 'city'])
244
245         self.assertEqual(sorted_by_id(results), sorted_by_id([
246             {'id': id_foo, 'name': 'foo', 'city': False},
247             {'id': id_bar, 'name': 'qux', 'city': 'tagtag'},
248             {'id': id_baz, 'name': 'quux', 'city': 'tag'}
249         ]))
250
251     def test_DELETE_command(self):
252         " deleted records are not returned at all. "
253         ids = [
254             self.partner.create(self.cr, UID, {'name': 'foo'}),
255             self.partner.create(self.cr, UID, {'name': 'bar'}),
256             self.partner.create(self.cr, UID, {'name': 'baz'})
257         ]
258         commands = [DELETE(ids[0]), DELETE(ids[1]), DELETE(ids[2])]
259
260         results = self.partner.resolve_2many_commands(
261             self.cr, UID, 'child_ids', commands, ['name'])
262
263         self.assertEqual(results, [])
264
265     def test_mixed_commands(self):
266         ids = [
267             self.partner.create(self.cr, UID, {'name': name})
268             for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply']
269         ]
270
271         results = self.partner.resolve_2many_commands(
272             self.cr, UID, 'child_ids', [
273                 CREATE({'name': 'foo'}),
274                 UPDATE(ids[0], {'name': 'bar'}),
275                 LINK_TO(ids[1]),
276                 DELETE(ids[2]),
277                 UPDATE(ids[3], {'name': 'quux',}),
278                 UPDATE(ids[4], {'name': 'corge'}),
279                 CREATE({'name': 'grault'}),
280                 LINK_TO(ids[5])
281             ], ['name'])
282
283         self.assertEqual(sorted_by_id(results), sorted_by_id([
284             {'name': 'foo'},
285             {'id': ids[0], 'name': 'bar'},
286             {'id': ids[1], 'name': 'baz'},
287             {'id': ids[3], 'name': 'quux'},
288             {'id': ids[4], 'name': 'corge'},
289             {'name': 'grault'},
290             {'id': ids[5], 'name': 'garply'}
291         ]))
292
293     def test_LINK_TO_pairs(self):
294         "LINK_TO commands can be written as pairs, instead of triplets"
295         ids = [
296             self.partner.create(self.cr, UID, {'name': 'foo'}),
297             self.partner.create(self.cr, UID, {'name': 'bar'}),
298             self.partner.create(self.cr, UID, {'name': 'baz'})
299         ]
300         commands = map(lambda id: (4, id), ids)
301
302         results = self.partner.resolve_2many_commands(
303             self.cr, UID, 'child_ids', commands, ['name'])
304
305         self.assertEqual(sorted_by_id(results), sorted_by_id([
306             {'id': ids[0], 'name': 'foo'},
307             {'id': ids[1], 'name': 'bar'},
308             {'id': ids[2], 'name': 'baz'}
309         ]))
310
311     def test_singleton_commands(self):
312         "DELETE_ALL can appear as a singleton"
313         results = self.partner.resolve_2many_commands(
314             self.cr, UID, 'child_ids', [DELETE_ALL()], ['name'])
315
316         self.assertEqual(results, [])
317
318 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: