diff --git a/addons/account/account.py b/addons/account/account.py index 4ee9da0ae30..89836be0637 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -28,7 +28,7 @@ import time import openerp from openerp import SUPERUSER_ID from openerp import tools -from openerp.osv import fields, osv +from openerp.osv import fields, osv, expression from openerp.tools.translate import _ from openerp.tools.float_utils import float_round @@ -579,15 +579,18 @@ class account_account(osv.osv): except: pass if name: - ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit) - if not ids: - ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit) - if not ids: - ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit) - if not ids and len(name.split()) >= 2: - #Separating code and name of account for searching - operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A. - ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit) + if operator not in expression.NEGATIVE_TERM_OPERATORS: + ids = self.search(cr, user, ['|', ('code', '=like', name+"%"), '|', ('shortcut', '=', name), ('name', operator, name)]+args, limit=limit) + if not ids and len(name.split()) >= 2: + #Separating code and name of account for searching + operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A. + ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit) + else: + ids = self.search(cr, user, ['&','!', ('code', '=like', name+"%"), ('name', operator, name)]+args, limit=limit) + # as negation want to restric, do if already have results + if ids and len(name.split()) >= 2: + operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A. + ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2), ('id', 'in', ids)]+ args, limit=limit) else: ids = self.search(cr, user, args, context=context, limit=limit) return self.name_get(cr, user, ids, context=context) @@ -1573,11 +1576,6 @@ class account_move(osv.osv): obj_analytic_line = self.pool.get('account.analytic.line') obj_move_line = self.pool.get('account.move.line') for move in self.browse(cr, uid, ids, context): - # Unlink old analytic lines on move_lines - for obj_line in move.line_id: - for obj in obj_line.analytic_lines: - obj_analytic_line.unlink(cr,uid,obj.id) - journal = move.journal_id amount = 0 line_ids = [] diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index d5c453ee9c7..cb242942656 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -193,6 +193,8 @@ class account_move_line(osv.osv): if obj_line.analytic_account_id: if not obj_line.journal_id.analytic_journal_id: raise osv.except_osv(_('No Analytic Journal!'),_("You have to define an analytic journal on the '%s' journal!") % (obj_line.journal_id.name, )) + if obj_line.analytic_lines: + acc_ana_line_obj.unlink(cr,uid,[obj.id for obj in obj_line.analytic_lines]) vals_line = self._prepare_analytic_line(cr, uid, obj_line, context=context) acc_ana_line_obj.create(cr, uid, vals_line) return True @@ -1207,20 +1209,6 @@ class account_move_line(osv.osv): if not ok: raise osv.except_osv(_('Bad Account!'), _('You cannot use this general account in this journal, check the tab \'Entry Controls\' on the related journal.')) - if vals.get('analytic_account_id',False): - if journal.analytic_journal_id: - vals['analytic_lines'] = [(0,0, { - 'name': vals['name'], - 'date': vals.get('date', time.strftime('%Y-%m-%d')), - 'account_id': vals.get('analytic_account_id', False), - 'unit_amount': vals.get('quantity', 1.0), - 'amount': vals.get('debit', 0.0) or vals.get('credit', 0.0), - 'general_account_id': vals.get('account_id', False), - 'journal_id': journal.analytic_journal_id.id, - 'ref': vals.get('ref', False), - 'user_id': uid - })] - result = super(account_move_line, self).create(cr, uid, vals, context=context) # CREATE Taxes if vals.get('account_tax_id', False): diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py index 11fe4186db6..02e9677ae03 100644 --- a/addons/account/tests/__init__.py +++ b/addons/account/tests/__init__.py @@ -1,4 +1,7 @@ from . import test_tax +from . import test_search -fast_suite = [test_tax, - ] +fast_suite = [ + test_tax, + test_search, +] diff --git a/addons/account/tests/test_search.py b/addons/account/tests/test_search.py new file mode 100644 index 00000000000..0e93da0c0bc --- /dev/null +++ b/addons/account/tests/test_search.py @@ -0,0 +1,60 @@ +from openerp.tests.common import TransactionCase + +class TestSearch(TransactionCase): + """Tests for search on name_search (account.account) + + The name search on account.account is quite complexe, make sure + we have all the correct results + """ + + def setUp(self): + super(TestSearch, self).setUp() + cr, uid = self.cr, self.uid + self.account_model = self.registry('account.account') + self.account_type_model = self.registry('account.account.type') + ac_ids = self.account_type_model.search(cr, uid, [], limit=1) + self.atax = (int(self.account_model.create(cr, uid, dict( + name="Tax Received", + code="121", + user_type=ac_ids[0], + ))), "121 Tax Received") + + self.apurchase = (int(self.account_model.create(cr, uid, dict( + name="Purchased Stocks", + code="1101", + user_type=ac_ids[0], + ))), "1101 Purchased Stocks") + + self.asale = (int(self.account_model.create(cr, uid, dict( + name="Product Sales", + code="200", + user_type=ac_ids[0], + ))), "200 Product Sales") + + self.all_ids = [self.atax[0], self.apurchase[0], self.asale[0]] + + def test_name_search(self): + cr, uid = self.cr, self.uid + atax_ids = self.account_model.name_search(cr, uid, name="Tax", operator='ilike', args=[('id', 'in', self.all_ids)]) + self.assertEqual(set([self.atax[0]]), set([a[0] for a in atax_ids]), "name_search 'ilike Tax' should have returned Tax Received account only") + + atax_ids = self.account_model.name_search(cr, uid, name="Tax", operator='not ilike', args=[('id', 'in', self.all_ids)]) + self.assertEqual(set([self.apurchase[0], self.asale[0]]), set([a[0] for a in atax_ids]), "name_search 'not ilike Tax' should have returned all but Tax Received account") + + apur_ids = self.account_model.name_search(cr, uid, name='1101', operator='ilike', args=[('id', 'in', self.all_ids)]) + self.assertEqual(set([self.apurchase[0]]), set([a[0] for a in apur_ids]), "name_search 'ilike 1101' should have returned Purchased Stocks account only") + + apur_ids = self.account_model.name_search(cr, uid, name='1101', operator='not ilike', args=[('id', 'in', self.all_ids)]) + self.assertEqual(set([self.atax[0], self.asale[0]]), set([a[0] for a in apur_ids]), "name_search 'not ilike 1101' should have returned all but Purchased Stocks account") + + asale_ids = self.account_model.name_search(cr, uid, name='200 Sales', operator='ilike', args=[('id', 'in', self.all_ids)]) + self.assertEqual(set([self.asale[0]]), set([a[0] for a in asale_ids]), "name_search 'ilike 200 Sales' should have returned Product Sales account only") + + asale_ids = self.account_model.name_search(cr, uid, name='200 Sales', operator='not ilike', args=[('id', 'in', self.all_ids)]) + self.assertEqual(set([self.atax[0], self.apurchase[0]]), set([a[0] for a in asale_ids]), "name_search 'not ilike 200 Sales' should have returned all but Product Sales account") + + asale_ids = self.account_model.name_search(cr, uid, name='Product Sales', operator='ilike', args=[('id', 'in', self.all_ids)]) + self.assertEqual(set([self.asale[0]]), set([a[0] for a in asale_ids]), "name_search 'ilike Product Sales' should have returned Product Sales account only") + + asale_ids = self.account_model.name_search(cr, uid, name='Product Sales', operator='not ilike', args=[('id', 'in', self.all_ids)]) + self.assertEqual(set([self.atax[0], self.apurchase[0]]), set([a[0] for a in asale_ids]), "name_search 'not ilike Product Sales' should have returned all but Product Sales account") diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 85d8ffca264..61f8195d319 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -247,13 +247,10 @@ class mail_thread(osv.AbstractModel): new = set(command[2]) # remove partners that are no longer followers - fol_ids = fol_obj.search(cr, SUPERUSER_ID, - [('res_model', '=', self._name), ('res_id', '=', id), ('partner_id', 'not in', list(new))]) - fol_obj.unlink(cr, SUPERUSER_ID, fol_ids) + self.message_unsubscribe(cr, uid, [id], list(old-new)) # add new followers - for partner_id in new - old: - fol_obj.create(cr, SUPERUSER_ID, {'res_model': self._name, 'res_id': id, 'partner_id': partner_id}) + self.message_subscribe(cr, uid, [id], list(new-old)) def _search_followers(self, cr, uid, obj, name, args, context): """Search function for message_follower_ids @@ -319,6 +316,7 @@ class mail_thread(osv.AbstractModel): """ if context is None: context = {} + thread_id = super(mail_thread, self).create(cr, uid, values, context=context) # automatic logging unless asked not to (mainly for various testing purpose) @@ -328,6 +326,7 @@ class mail_thread(osv.AbstractModel): # subscribe uid unless asked not to if not context.get('mail_create_nosubscribe'): self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context) + # auto_subscribe: take values and defaults into account create_values = dict(values) for key, val in context.iteritems(): @@ -1494,35 +1493,33 @@ class mail_thread(osv.AbstractModel): else: self.check_access_rights(cr, uid, 'write') - for record in self.browse(cr, SUPERUSER_ID, ids, context=context): - existing_pids = set([f.id for f in record.message_follower_ids - if f.id in partner_ids]) + existing_pids_dict = {} + fol_ids = mail_followers_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)]) + for fol in mail_followers_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context): + existing_pids_dict.setdefault(fol.res_id, set()).add(fol.partner_id.id) + + # subtype_ids specified: update already subscribed partners + if subtype_ids and fol_ids: + mail_followers_obj.write(cr, SUPERUSER_ID, fol_ids, {'subtype_ids': [(6, 0, subtype_ids)]}, context=context) + # subtype_ids not specified: do not update already subscribed partner, fetch default subtypes for new partners + if subtype_ids is None: + subtype_ids = subtype_obj.search( + cr, uid, [ + ('default', '=', True), '|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context) + + for id in ids: + existing_pids = existing_pids_dict.get(id, set()) new_pids = set(partner_ids) - existing_pids - # subtype_ids specified: update already subscribed partners - if subtype_ids and existing_pids: - fol_ids = mail_followers_obj.search(cr, SUPERUSER_ID, [ - ('res_model', '=', self._name), - ('res_id', '=', record.id), - ('partner_id', 'in', list(existing_pids)), - ], context=context) - mail_followers_obj.write(cr, SUPERUSER_ID, fol_ids, {'subtype_ids': [(6, 0, subtype_ids)]}, context=context) - # subtype_ids not specified: do not update already subscribed partner, fetch default subtypes for new partners - elif subtype_ids is None: - subtype_ids = subtype_obj.search(cr, uid, [ - ('default', '=', True), - '|', - ('res_model', '=', self._name), - ('res_model', '=', False) - ], context=context) # subscribe new followers for new_pid in new_pids: - mail_followers_obj.create(cr, SUPERUSER_ID, { - 'res_model': self._name, - 'res_id': record.id, - 'partner_id': new_pid, - 'subtype_ids': [(6, 0, subtype_ids)], - }, context=context) + mail_followers_obj.create( + cr, SUPERUSER_ID, { + 'res_model': self._name, + 'res_id': id, + 'partner_id': new_pid, + 'subtype_ids': [(6, 0, subtype_ids)], + }, context=context) return True @@ -1541,7 +1538,14 @@ class mail_thread(osv.AbstractModel): self.check_access_rights(cr, uid, 'read') else: self.check_access_rights(cr, uid, 'write') - return self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context) + fol_obj = self.pool['mail.followers'] + fol_ids = fol_obj.search( + cr, SUPERUSER_ID, [ + ('res_model', '=', self._name), + ('res_id', 'in', ids), + ('partner_id', 'in', partner_ids) + ], context=context) + return fol_obj.unlink(cr, SUPERUSER_ID, fol_ids, context=context) def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=['user_id'], context=None): """ Returns the list of relational fields linking to res.users that should diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 67fde81136a..7071a862413 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -560,6 +560,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.handler = function(e){ if(e.which === 13){ //ignore returns + e.preventDefault(); return; } diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index dbfd1a82ced..f6bb00fed5f 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -242,6 +242,8 @@ class ir_attachment(osv.osv): # performed in batch as much as possible. ima = self.pool.get('ir.model.access') for model, targets in model_attachments.iteritems(): + if model not in self.pool: + continue if not ima.check(cr, uid, model, 'read', False): # remove all corresponding attachment ids for attach_id in itertools.chain(*targets.values()): diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 124343a9e86..a9140a1454b 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -533,15 +533,19 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): return res def execute(self, cr, uid, ids, context=None): + if uid != SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager'): + raise openerp.exceptions.AccessError(_("Only administrators can change the settings")) + ir_values = self.pool['ir.values'] ir_module = self.pool['ir.module.module'] + classified = self._get_classified_fields(cr, uid, context) config = self.browse(cr, uid, ids[0], context) # default values fields for name, model, field in classified['default']: - ir_values.set_default(cr, uid, model, field, config[name]) + ir_values.set_default(cr, SUPERUSER_ID, model, field, config[name]) # group fields: modify group / implied groups for name, group, implied_group in classified['group']: diff --git a/openerp/addons/base/res/res_users_view.xml b/openerp/addons/base/res/res_users_view.xml index a5f988428e9..f950b218867 100644 --- a/openerp/addons/base/res/res_users_view.xml +++ b/openerp/addons/base/res/res_users_view.xml @@ -239,7 +239,7 @@
- +

diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index c9509a1e4b9..a9498b1ff71 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -601,8 +601,10 @@ class one2many(_column): else: cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],)) elif act[0] == 4: - # Must use write() to recompute parent_store structure if needed - obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {}) + cr.execute("select 1 from {0} where id=%s and {1}=%s".format(_table, self._fields_id), (act[1], id)) + if not cr.fetchone(): + # Must use write() to recompute parent_store structure if needed and check access rules + obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {}) elif act[0] == 5: reverse_rel = obj._all_columns.get(self._fields_id) assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o' diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 71053a02cae..610d0a5d503 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -3681,11 +3681,6 @@ class BaseModel(object): if fields_to_read is None: fields_to_read = self._columns.keys() - # Construct a clause for the security rules. - # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses, - # or will at least contain self._table. - rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context) - # all inherited fields + all non inherited fields for which the attribute whose name is in load is True fields_pre = [f for f in fields_to_read if f == self.CONCURRENCY_CHECK_FIELD @@ -3706,6 +3701,11 @@ class BaseModel(object): return 'length(%s) as "%s"' % (f_qual, f) return f_qual + # Construct a clause for the security rules. + # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses, + # or will at least contain self._table. + rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context) + fields_pre2 = map(convert_field, fields_pre) order_by = self._parent_order or self._order select_fields = ','.join(fields_pre2 + ['%s.id' % self._table]) @@ -3720,6 +3720,7 @@ class BaseModel(object): self._check_record_rules_result_count(cr, user, sub_ids, result_ids, 'read', context=context) res.extend(results) else: + self.check_access_rule(cr, user, ids, 'read', context=context) res = map(lambda x: {'id': x}, ids) if context.get('lang'): diff --git a/openerp/tools/convert.py b/openerp/tools/convert.py index 47f75e43fe8..6868b4fd897 100644 --- a/openerp/tools/convert.py +++ b/openerp/tools/convert.py @@ -952,7 +952,7 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', result, rows, warning_msg, dummy = registry[model].import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial) if result < 0: # Report failed import and abort module install - raise Exception(_('Module loading failed: file %s/%s could not be processed:\n %s') % (module, fname, warning_msg)) + raise Exception(_('Module loading %s failed: file %s could not be processed:\n %s') % (module, fname, warning_msg)) if config.get('import_partial'): data = pickle.load(file(config.get('import_partial'))) data[fname_partial] = 0