diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index 525c7e2e334..2503adc5525 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -199,7 +199,7 @@ cf - Congo, The Democratic Republic of the + Congo, Democratic Republic of the cd diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 30bbf360346..36b813b4879 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -54,11 +54,11 @@ class ir_attachment(osv.osv): ima.check(cr, uid, model, mode) self.pool.get(model).check_access_rule(cr, uid, mids, mode, context=context) - def search(self, cr, uid, args, offset=0, limit=None, order=None, - context=None, count=False): - ids = super(ir_attachment, self).search(cr, uid, args, offset=offset, - limit=limit, order=order, - context=context, count=False) + def _search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None): + ids = super(ir_attachment, self)._search(cr, uid, args, offset=offset, + limit=limit, order=order, + context=context, count=count, + access_rights_uid=access_rights_uid) if not ids: if count: return 0 diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index 7d9403e7d91..ecb41b32592 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2011 OpenERP S.A () +# Copyright (C) 2011-2012 OpenERP S.A () # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -232,6 +232,11 @@ class ir_mail_server(osv.osv): if user: # Attempt authentication - will raise if AUTH service not supported + # The user/password must be converted to bytestrings in order to be usable for + # certain hashing schemes, like HMAC. + # See also bug #597143 and python issue #5285 + user = tools.ustr(user).encode('utf-8') + password = tools.ustr(password).encode('utf-8') connection.login(user, password) return connection @@ -364,7 +369,6 @@ class ir_mail_server(osv.osv): :param smtp_user: optional SMTP user, if mail_server_id is not passed :param smtp_password: optional SMTP password to use, if mail_server_id is not passed :param smtp_debug: optional SMTP debug flag, if mail_server_id is not passed - :param debug: whether to turn on the SMTP level debugging, output to DEBUG log level :return: the Message-ID of the message that was just sent, if successfully sent, otherwise raises MailDeliveryException and logs root cause. """ diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index e8ce3adb592..fdce0d7c483 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -35,12 +35,14 @@ import pooler _logger = logging.getLogger(__name__) def _get_fields_type(self, cr, uid, context=None): + # Avoid too many nested `if`s below, as RedHat's Python 2.6 + # break on it. See bug 939653. return sorted([(k,k) for k,v in fields.__dict__.iteritems() - if type(v) == types.TypeType - if issubclass(v, fields._column) - if v != fields._column - if not v._deprecated - if not issubclass(v, fields.function)]) + if type(v) == types.TypeType and \ + issubclass(v, fields._column) and \ + v != fields._column and \ + not v._deprecated and \ + not issubclass(v, fields.function)]) def _in_modules(self, cr, uid, ids, field_name, arg, context=None): #pseudo-method used by fields.function in ir.model/ir.model.fields diff --git a/openerp/addons/base/ir/ir_rule.py b/openerp/addons/base/ir/ir_rule.py index 0626b2b9c1e..6b069e0b9d0 100644 --- a/openerp/addons/base/ir/ir_rule.py +++ b/openerp/addons/base/ir/ir_rule.py @@ -75,7 +75,7 @@ class ir_rule(osv.osv): _columns = { 'name': fields.char('Name', size=128, select=1), - 'model_id': fields.many2one('ir.model', 'Object',select=1, required=True), + 'model_id': fields.many2one('ir.model', 'Object',select=1, required=True, ondelete="cascade"), 'global': fields.function(_get_value, string='Global', type='boolean', store=True, help="If no group is specified the rule is global and applied to everyone"), 'groups': fields.many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id', 'Groups'), 'domain_force': fields.text('Domain'), diff --git a/openerp/addons/base/module/wizard/base_module_upgrade.py b/openerp/addons/base/module/wizard/base_module_upgrade.py index 3b8671b0ebb..6cbc63341f5 100644 --- a/openerp/addons/base/module/wizard/base_module_upgrade.py +++ b/openerp/addons/base/module/wizard/base_module_upgrade.py @@ -42,6 +42,9 @@ class base_module_upgrade(osv.osv_memory): @return: New arch of view. """ res = super(base_module_upgrade, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False) + if view_type != 'form': + return res + record_id = context and context.get('active_id', False) or False active_model = context.get('active_model') if (not record_id) or (not active_model): diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index ce16869a304..430512c5260 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -151,7 +151,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= _logger.debug('loading %d packages...', len(graph)) # get db timestamp - cr.execute("select now()::timestamp") + cr.execute("select (now() at time zone 'UTC')::timestamp") dt_before_load = cr.fetchone()[0] # register, instantiate and initialize models for each modules diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 7c1761a4aec..bb654caea4d 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -364,7 +364,15 @@ class time(_column): class binary(_column): _type = 'binary' _symbol_c = '%s' - _symbol_f = lambda symb: symb and Binary(symb) or None + + # Binary values may be byte strings (python 2.6 byte array), but + # the legacy OpenERP convention is to transfer and store binaries + # as base64-encoded strings. The base64 string may be provided as a + # unicode in some circumstances, hence the str() cast in symbol_f. + # This str coercion will only work for pure ASCII unicode strings, + # on purpose - non base64 data must be passed as a 8bit byte strings. + _symbol_f = lambda symb: symb and Binary(str(symb)) or None + _symbol_set = (_symbol_c, _symbol_f) _symbol_get = lambda self, x: x and str(x) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 8901cf5ed87..32b039f6598 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -750,7 +750,7 @@ class BaseModel(object): name_id = 'model_'+self._name.replace('.', '_') cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, context['module'])) if not cr.rowcount: - cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \ + cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, (now() at time zone 'UTC'), (now() at time zone 'UTC'), %s, %s, %s)", \ (name_id, context['module'], 'ir.model', model_id) ) @@ -816,7 +816,7 @@ class BaseModel(object): cr.execute("select name from ir_model_data where name=%s", (name1,)) if cr.fetchone(): name1 = name1 + "_" + str(id) - cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \ + cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, (now() at time zone 'UTC'), (now() at time zone 'UTC'), %s, %s, %s)", \ (name1, context['module'], 'ir.model.fields', id) ) else: @@ -872,11 +872,11 @@ class BaseModel(object): for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]): parent_model = pool.get(parent_name) - if not getattr(cls, '_original_module', None) and name == parent_model._name: - cls._original_module = parent_model._original_module if not parent_model: raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n' 'You may need to add a dependency on the parent class\' module.' % (name, parent_name)) + if not getattr(cls, '_original_module', None) and name == parent_model._name: + cls._original_module = parent_model._original_module parent_class = parent_model.__class__ nattr = {} for s in attributes: @@ -2690,7 +2690,7 @@ class BaseModel(object): elif val in dict(self._columns[field].selection(self, cr, uid, context=context)): return raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s.%s" is not in the selection') % (value, self._table, field)) + _('The value "%s" for the field "%s.%s" is not in the selection') % (value, self._table, field)) def _check_removed_columns(self, cr, log=False): # iterate on the database columns to drop the NOT NULL constraints @@ -2733,6 +2733,50 @@ class BaseModel(object): _schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s", source_table, source_field, dest_model._table, ondelete) + def _drop_constraint(self, cr, source_table, constraint_name): + cr.execute("ALTER TABLE %s DROP CONSTRAINT %s" % (source_table,constraint_name)) + + def _m2o_fix_foreign_key(self, cr, source_table, source_field, dest_model, ondelete): + # Find FK constraint(s) currently established for the m2o field, + # and see whether they are stale or not + cr.execute("""SELECT confdeltype as ondelete_rule, conname as constraint_name, + cl2.relname as foreign_table + FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, + pg_attribute as att1, pg_attribute as att2 + WHERE con.conrelid = cl1.oid + AND cl1.relname = %s + AND con.confrelid = cl2.oid + AND array_lower(con.conkey, 1) = 1 + AND con.conkey[1] = att1.attnum + AND att1.attrelid = cl1.oid + AND att1.attname = %s + AND array_lower(con.confkey, 1) = 1 + AND con.confkey[1] = att2.attnum + AND att2.attrelid = cl2.oid + AND att2.attname = %s + AND con.contype = 'f'""", (source_table, source_field, 'id')) + constraints = cr.dictfetchall() + if constraints: + if len(constraints) == 1: + # Is it the right constraint? + cons, = constraints + if cons['ondelete_rule'] != POSTGRES_CONFDELTYPES.get((ondelete or 'set null').upper(), 'a')\ + or cons['foreign_table'] != dest_model._table: + _schema.debug("Table '%s': dropping obsolete FK constraint: '%s'", + source_table, cons['constraint_name']) + self._drop_constraint(cr, source_table, cons['constraint_name']) + self._m2o_add_foreign_key_checked(source_field, dest_model, ondelete) + # else it's all good, nothing to do! + else: + # Multiple FKs found for the same field, drop them all, and re-create + for cons in constraints: + _schema.debug("Table '%s': dropping duplicate FK constraints: '%s'", + source_table, cons['constraint_name']) + self._drop_constraint(cr, source_table, cons['constraint_name']) + self._m2o_add_foreign_key_checked(source_field, dest_model, ondelete) + + + def _auto_init(self, cr, context=None): """ @@ -2932,31 +2976,8 @@ class BaseModel(object): if isinstance(f, fields.many2one): dest_model = self.pool.get(f._obj) - ref = dest_model._table - if ref != 'ir_actions': - cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, ' - 'pg_attribute as att1, pg_attribute as att2 ' - 'WHERE con.conrelid = cl1.oid ' - 'AND cl1.relname = %s ' - 'AND con.confrelid = cl2.oid ' - 'AND cl2.relname = %s ' - 'AND array_lower(con.conkey, 1) = 1 ' - 'AND con.conkey[1] = att1.attnum ' - 'AND att1.attrelid = cl1.oid ' - 'AND att1.attname = %s ' - 'AND array_lower(con.confkey, 1) = 1 ' - 'AND con.confkey[1] = att2.attnum ' - 'AND att2.attrelid = cl2.oid ' - 'AND att2.attname = %s ' - "AND con.contype = 'f'", (self._table, ref, k, 'id')) - res2 = cr.dictfetchall() - if res2: - if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get((f.ondelete or 'set null').upper(), 'a'): - cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"') - self._m2o_add_foreign_key_checked(k, dest_model, f.ondelete) - cr.commit() - _schema.debug("Table '%s': column '%s': XXX", - self._table, k) + if dest_model._table != 'ir_actions': + self._m2o_fix_foreign_key(cr, self._table, k, dest_model, f.ondelete) # The field doesn't exist in database. Create it if necessary. else: @@ -3152,6 +3173,9 @@ class BaseModel(object): _sql_constraints. """ + def unify_cons_text(txt): + return txt.lower().replace(', ',',').replace(' (','(') + for (key, con, _) in self._sql_constraints: conname = '%s_%s' % (self._table, key) @@ -3181,7 +3205,7 @@ 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 con.lower() not in [item['condef'].lower() for item in existing_constraints]: + elif unify_cons_text(con) not in [unify_cons_text(item['condef']) for item in existing_constraints]: # 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(), ) @@ -3416,8 +3440,8 @@ class BaseModel(object): return "date_trunc('second', %s) as %s" % (f_qual, f) if f == self.CONCURRENCY_CHECK_FIELD: if self._log_access: - return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,) - return "now()::timestamp AS %s" % (f,) + return "COALESCE(%s.write_date, %s.create_date, (now() at time zone 'UTC'))::timestamp AS %s" % (self._table, self._table, f,) + return "(now() at time zone 'UTC')::timestamp AS %s" % (f,) if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False): return 'length(%s) as "%s"' % (f_qual, f) return f_qual @@ -3598,7 +3622,7 @@ class BaseModel(object): return if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access): return - check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)" + check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp)" for sub_ids in cr.split_for_in_conditions(ids): ids_to_check = [] for id in sub_ids: @@ -3882,7 +3906,7 @@ class BaseModel(object): if self._log_access: upd0.append('write_uid=%s') - upd0.append('write_date=now()') + upd0.append("write_date=(now() at time zone 'UTC')") upd1.append(user) if len(upd0): @@ -4149,7 +4173,7 @@ class BaseModel(object): self._check_selection_field_value(cr, user, field, vals[field], context=context) if self._log_access: upd0 += ',create_uid,create_date' - upd1 += ',%s,now()' + upd1 += ",%s,(now() at time zone 'UTC')" upd2.append(user) cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2)) self.check_access_rule(cr, user, [id_new], 'create', context=context) @@ -4382,13 +4406,11 @@ class BaseModel(object): domain = domain[:] # if the object has a field named 'active', filter out all inactive # records unless they were explicitely asked for - if 'active' in self._columns and (active_test and context.get('active_test', True)): + if 'active' in self._all_columns and (active_test and context.get('active_test', True)): if domain: - active_in_args = False - for a in domain: - if a[0] == 'active': - active_in_args = True - if not active_in_args: + # the item[0] trick below works for domain items and '&'/'|'/'!' + # operators too + if not any(item[0] == 'active' for item in domain): domain.insert(0, ('active', '=', 1)) else: domain = [('active', '=', 1)] @@ -4852,15 +4874,15 @@ class BaseModel(object): def _transient_clean_rows_older_than(self, cr, seconds): assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name cr.execute("SELECT id FROM " + self._table + " WHERE" - " COALESCE(write_date, create_date, now())::timestamp <" - " (now() - interval %s)", ("%s seconds" % seconds,)) + " COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp <" + " ((now() at time zone 'UTC') - interval %s)", ("%s seconds" % seconds,)) ids = [x[0] for x in cr.fetchall()] self.unlink(cr, SUPERUSER_ID, ids) def _transient_clean_old_rows(self, cr, count): assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name cr.execute( - "SELECT id, COALESCE(write_date, create_date, now())::timestamp" + "SELECT id, COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp" " AS t FROM " + self._table + " ORDER BY t LIMIT %s", (count,)) ids = [x[0] for x in cr.fetchall()] diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index a5c72900fc7..343e03cd825 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -3,7 +3,7 @@ # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). -# Copyright (C) 2010 OpenERP s.a. (). +# Copyright (C) 2010-2012 OpenERP s.a. (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -383,7 +383,7 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non res = mail_server_pool.send_email(cr, uid or 1, email_msg, mail_server_id=None, smtp_server=smtp_server, smtp_port=smtp_port, smtp_user=smtp_user, smtp_password=smtp_password, - smtp_encryption=('ssl' if ssl else None), debug=debug) + smtp_encryption=('ssl' if ssl else None), smtp_debug=debug) except Exception: _logger.exception("tools.email_send failed to deliver email") return False diff --git a/openerp/tools/sql.py b/openerp/tools/sql.py index 445eebb0bb8..3397d1ecbd3 100644 --- a/openerp/tools/sql.py +++ b/openerp/tools/sql.py @@ -20,9 +20,7 @@ ############################################################################## def drop_view_if_exists(cr, viewname): - cr.execute("select count(1) from pg_class where relkind=%s and relname=%s", ('v', viewname,)) - if cr.fetchone()[0]: - cr.execute("DROP view %s" % (viewname,)) - cr.commit() + cr.execute("DROP view IF EXISTS %s CASCADE" % (viewname,)) + cr.commit() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: