diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index a6bb38b5df7..21639aba2e0 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -24,6 +24,8 @@ import logging import operator import os import time +import datetime +import dateutil import openerp from openerp import SUPERUSER_ID @@ -910,6 +912,33 @@ class ir_actions_server(osv.osv): if action.link_new_record and action.link_field_id: self.pool[action.model_id.model].write(cr, uid, [context.get('active_id')], {action.link_field_id.name: res_id}) + def _eval_context_for_action(self, cr, uid, action, context=None): + if context is None: + context = {} + model = self.pool[action.model_id.model] + active_id = context.get('active_id') + active_ids = context.get('active_ids', [active_id] if active_id else []) + target_record = None + if context.get('active_model') == action.model_id.model and active_id: + context = dict(context, active_ids=active_ids, active_id=active_id) + target_record = model.browse(cr, uid, active_id, context=context) if active_id else None + user = self.pool['res.users'].browse(cr, uid, uid) + eval_context = { + 'self': model, + 'object': target_record, + 'obj': target_record, + 'pool': self.pool, + 'time': time, + 'datetime': datetime, + 'dateutil': dateutil, + 'cr': cr, + 'uid': uid, + 'user': user, + 'context': context, + } + return eval_context + + def run(self, cr, uid, ids, context=None): """ Runs the server action. For each server action, the condition is checked. Note that a void (``False``) condition is considered as always @@ -932,33 +961,14 @@ class ir_actions_server(osv.osv): if context is None: context = {} res = False - user = self.pool.get('res.users').browse(cr, uid, uid) - active_ids = context.get('active_ids', [context.get('active_id')]) for action in self.browse(cr, uid, ids, context): - obj_pool = self.pool[action.model_id.model] - obj = None - if context.get('active_model') == action.model_id.model and context.get('active_id'): - obj = obj_pool.browse(cr, uid, context['active_id'], context=context) - - # evaluation context for python strings to evaluate - eval_context = { - 'self': obj_pool, - 'object': obj, - 'obj': obj, - 'pool': self.pool, - 'time': time, - 'cr': cr, - 'uid': uid, - 'user': user, - } + eval_context = self._eval_context_for_action(cr, uid, action, context) condition = action.condition if condition is False: # Void (aka False) conditions are considered as True condition = True if hasattr(self, 'run_action_%s_multi' % action.state): - # set active_ids in context only needed if one active_id - run_context = dict(context, active_ids=active_ids) - eval_context["context"] = run_context + run_context = eval_context['context'] expr = eval(str(condition), eval_context) if not expr: continue @@ -968,6 +978,8 @@ class ir_actions_server(osv.osv): elif hasattr(self, 'run_action_%s' % action.state): func = getattr(self, 'run_action_%s' % action.state) + active_id = context.get('active_id') + active_ids = context.get('active_ids', [active_id] if active_id else []) for active_id in active_ids: # run context dedicated to a particular active_id run_context = dict(context, active_ids=[active_id], active_id=active_id) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index ad017381409..b2aba694078 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -906,6 +906,8 @@ class change_password_wizard(osv.TransientModel): for user in wizard.user_ids: user_ids.append(user.id) self.pool.get('change.password.user').change_password_button(cr, uid, user_ids, context=context) + # don't keep temporary password copies in the database longer than necessary + self.pool.get('change.password.user').unlink(cr, uid, user_ids) return { 'type': 'ir.actions.act_window_close', } diff --git a/openerp/addons/base/tests/test_base.py b/openerp/addons/base/tests/test_base.py index 20382737529..89f935dc7a2 100644 --- a/openerp/addons/base/tests/test_base.py +++ b/openerp/addons/base/tests/test_base.py @@ -289,6 +289,7 @@ class test_base(common.TransactionCase): {'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend'}, {'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend'}, {'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper'}, + {'name': 'Nab', 'login': 'nab', 'color': 2, 'function': '5$ Wrench'}, ]: self.res_users.create(cr, uid, user_data) @@ -298,6 +299,14 @@ class test_base(common.TransactionCase): self.assertIn('color', group_data, "Aggregated data for the column 'color' is not present in read_group return values") self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'") + groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve'))], fields=['name', 'color'], groupby='name', orderby='name DESC, color asc') + self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field") + self.assertEqual([user['name'] for user in groups_data], ['Eve', 'Bob', 'Alice'], 'Incorrect ordering of the list') + + groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve', 'nab'))], fields=['function', 'color'], groupby='function', orderby='color ASC') + self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field") + self.assertEqual(groups_data, sorted(groups_data, key=lambda x: x['color']), 'Incorrect ordering of the list') + class test_partner_recursion(common.TransactionCase): def setUp(self): diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 190453d477a..478f982cd69 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1461,11 +1461,9 @@ class property(function): def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None): prop = obj.pool.get('ir.property') vids = [obj._name + ',' + str(oid) for oid in ids] - def_id = self._field_get(cr, uid, obj._name, prop_name[0]) - company = obj.pool.get('res.company') - cid = company._company_default_get(cr, uid, obj._name, def_id, context=context) - domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name), ('company_id', '=', cid)] - #domain = prop._get_domain(cr, uid, prop_name, obj._name, context) + domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)] + if context and context.get('company_id'): + domain += [('company_id', '=', context.get('company_id'))] if vids: domain = [('res_id', 'in', vids)] + domain return prop.search(cr, uid, domain, context=context) @@ -1475,7 +1473,12 @@ class property(function): if context is None: context = {} - nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context) + def_id = self._field_get(cr, uid, obj._name, prop_name) + company = obj.pool.get('res.company') + cid = company._company_default_get(cr, uid, obj._name, def_id, context=context) + # TODO for trunk: add new parameter company_id to _get_by_id method + context_company = dict(context, company_id=cid) + nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context_company) if nids: cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),)) @@ -1489,10 +1492,6 @@ class property(function): property_create = True if property_create: - def_id = self._field_get(cr, uid, obj._name, prop_name) - company = obj.pool.get('res.company') - cid = company._company_default_get(cr, uid, obj._name, def_id, - context=context) propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id, context=context) prop = obj.pool.get('ir.property') diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 133ac5e6653..9fee28359f0 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -770,8 +770,6 @@ class BaseModel(object): (name_id, context['module'], 'ir.model', model_id) ) - cr.commit() - cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,)) cols = {} for rec in cr.dictfetchall(): @@ -838,7 +836,6 @@ class BaseModel(object): for key, val in vals.items(): if cols[k][key] != vals[key]: cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name'])) - cr.commit() cr.execute("""UPDATE ir_model_fields SET model_id=%s, field_description=%s, ttype=%s, relation=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s, translate=%s, serialization_field_id=%s @@ -849,7 +846,6 @@ class BaseModel(object): vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], bool(vals['translate']), vals['serialization_field_id'], vals['model'], vals['name'] )) break - cr.commit() # # Goal: try to apply inheritance at the instanciation level and @@ -2189,6 +2185,32 @@ class BaseModel(object): r['__fold'] = folded.get(r[groupby] and r[groupby][0], False) return result + def _read_group_generate_order_by(self, orderby, aggregated_fields, groupby, query): + """ + Generates the ORDER BY sql clause for the read group method. Adds the missing JOIN clause + to the query if order should be computed against m2o field. + :param orderby: the orderby definition in the form "%(field)s %(order)s" + :param aggregated_fields: list of aggregated fields in the query + :param groupby: the current groupby field name + :param query: the query object used to construct the query afterwards + """ + orderby_list = [] + ob = '' + for order_splits in orderby.split(','): + order_split = order_splits.split() + orderby_field = order_split[0] + orderby_dir = len(order_split) == 2 and order_split[1].upper() == 'ASC' and 'ASC' or 'DESC' + if orderby_field == groupby: + ob = self._generate_order_by(order_splits, query).replace('ORDER BY ', '') + orderby_list.append(ob) + elif orderby_field in aggregated_fields: + orderby_list.append('%s %s' % (orderby_field,orderby_dir)) + + if orderby_list: + return ' ORDER BY %s' % (','.join(orderby_list)), ob and ob.split()[0] or '' + else: + return '', '' + def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): """ Get the list of records in list view grouped by the given ``groupby`` fields @@ -2301,6 +2323,12 @@ class BaseModel(object): qualified_field = self._inherits_join_calc(f, query) flist += "%s(%s) AS %s" % (group_operator, qualified_field, f) + order = orderby or groupby + orderby_clause = '' + ob = '' + if order: + orderby_clause, ob = self._read_group_generate_order_by(order, aggregated_fields, groupby, query) + gb = groupby and (' GROUP BY ' + qualified_groupby_field) or '' from_clause, where_clause, where_clause_params = query.get_sql() @@ -2309,20 +2337,21 @@ class BaseModel(object): offset_str = offset and ' offset %d' % offset or '' if len(groupby_list) < 2 and context.get('group_by_no_leaf'): group_count = '_' - cr.execute('SELECT min(%s.id) AS id, count(%s.id) AS %s_count' % (self._table, self._table, group_count) + (flist and ',') + flist + ' FROM ' + from_clause + where_clause + gb + limit_str + offset_str, where_clause_params) + cr.execute('SELECT min(%s.id) AS id, count(%s.id) AS %s_count' % (self._table, self._table, group_count) + (flist and ',') + flist + ' FROM ' + from_clause + where_clause + gb + (ob and ',') + ob + orderby_clause + limit_str + offset_str, where_clause_params) alldata = {} groupby = group_by - for r in cr.dictfetchall(): + + fetched_data = cr.dictfetchall() + + data_ids = [] + for r in fetched_data: for fld, val in r.items(): if val is None: r[fld] = False alldata[r['id']] = r + data_ids.append(r['id']) del r['id'] - order = orderby or groupby - data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=order, context=context) - # the IDs of records that have groupby field value = False or '' should be included too - data_ids += set(alldata.keys()).difference(data_ids) if groupby: data = self.read(cr, uid, data_ids, [groupby], context=context) @@ -4628,24 +4657,22 @@ class BaseModel(object): blacklist_given_fields(self) - fields_to_read = [f for f in self.check_field_access_rights(cr, uid, 'read', None) - if f not in blacklist] - data = self.read(cr, uid, [id], fields_to_read, context=context) + + fields_to_copy = dict((f,fi) for f, fi in self._all_columns.iteritems() + if f not in default + if f not in blacklist + if not isinstance(fi.column, fields.function)) + + data = self.read(cr, uid, [id], fields_to_copy.keys(), context=context) if data: data = data[0] else: - raise IndexError(_("Record #%d of %s not found, cannot copy!") % (id, self._name)) + raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name)) res = dict(default) - for f, colinfo in self._all_columns.items(): + for f, colinfo in fields_to_copy.iteritems(): field = colinfo.column - if f in default: - pass - elif f in blacklist: - pass - elif isinstance(field, fields.function): - pass - elif field._type == 'many2one': + if field._type == 'many2one': res[f] = data[f] and data[f][0] elif field._type == 'one2many': other = self.pool[field._obj] diff --git a/openerp/service/db.py b/openerp/service/db.py index fa1bc1336fc..ed890a477ea 100644 --- a/openerp/service/db.py +++ b/openerp/service/db.py @@ -311,7 +311,7 @@ def exp_list(document=False): cr.execute("select datname from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in %s order by datname", (db_user, templates_list)) else: cr.execute("select datname from pg_database where datname not in %s order by datname", (templates_list,)) - res = [str(name) for (name,) in cr.fetchall()] + res = [tools.ustr(name) for (name,) in cr.fetchall()] except Exception: res = [] res.sort() diff --git a/openerp/tests/test_fields.py b/openerp/tests/test_fields.py index 774bbf41fb2..cb05861d301 100644 --- a/openerp/tests/test_fields.py +++ b/openerp/tests/test_fields.py @@ -121,4 +121,62 @@ class TestRelatedField(common.TransactionCase): # restore res.partner fields self.partner._columns = old_columns -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + +class TestPropertyField(common.TransactionCase): + + def setUp(self): + super(TestPropertyField, self).setUp() + self.user = self.registry('res.users') + self.partner = self.registry('res.partner') + self.company = self.registry('res.company') + self.country = self.registry('res.country') + self.property = self.registry('ir.property') + self.imd = self.registry('ir.model.data') + + def test_1_property_multicompany(self): + cr, uid = self.cr, self.uid + + parent_company_id = self.imd.get_object_reference(cr, uid, 'base', 'main_company')[1] + country_be = self.imd.get_object_reference(cr, uid, 'base', 'be')[1] + country_fr = self.imd.get_object_reference(cr, uid, 'base', 'fr')[1] + group_partner_manager = self.imd.get_object_reference(cr, uid, 'base', 'group_partner_manager')[1] + group_multi_company = self.imd.get_object_reference(cr, uid, 'base', 'group_multi_company')[1] + + sub_company = self.company.create(cr, uid, {'name': 'MegaCorp', 'parent_id': parent_company_id}) + alice = self.user.create(cr, uid, {'name': 'Alice', + 'login':'alice', + 'email':'alice@youcompany.com', + 'company_id':parent_company_id, + 'company_ids':[(6, 0, [parent_company_id, sub_company])], + 'country_id':country_be, + 'groups_id': [(6, 0, [group_partner_manager, group_multi_company])] + }) + bob = self.user.create(cr, uid, {'name': 'Bob', + 'login':'bob', + 'email':'bob@megacorp.com', + 'company_id':sub_company, + 'company_ids':[(6, 0, [parent_company_id, sub_company])], + 'country_id':country_fr, + 'groups_id': [(6, 0, [group_partner_manager, group_multi_company])] + }) + + self.partner._columns = dict(self.partner._columns) + self.partner._columns.update({ + 'property_country': fields.property(type='many2one', relation="res.country", string="Country by company"), + }) + self.partner._all_columns.update({ + 'property_country': fields.column_info('property_country', self.partner._columns['property_country'], None, None, None), + }) + self.partner._field_create(cr) + + partner_id = self.partner.create(cr, alice, { + 'name': 'An International Partner', + 'email': 'partner@example.com', + 'company_id': parent_company_id, + }) + self.partner.write(cr, bob, [partner_id], {'property_country': country_fr}) + self.assertEqual(self.partner.browse(cr, bob, partner_id).property_country.id, country_fr, "Bob does not see the value he has set on the property field") + + self.partner.write(cr, alice, [partner_id], {'property_country': country_be}) + self.assertEqual(self.partner.browse(cr, alice, partner_id).property_country.id, country_be, "Alice does not see the value he has set on the property field") + self.assertEqual(self.partner.browse(cr, bob, partner_id).property_country.id, country_fr, "Changes made by Alice have overwritten Bob's value") diff --git a/openerp/tools/image.py b/openerp/tools/image.py index 5e5055f252d..196ace2da60 100644 --- a/openerp/tools/image.py +++ b/openerp/tools/image.py @@ -25,7 +25,7 @@ except ImportError: import StringIO from PIL import Image -from PIL import ImageOps +from PIL import ImageEnhance from random import randint # ---------------------------------------- @@ -81,8 +81,13 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file return base64_source if image.size != size: - # If you need faster thumbnails you may use use Image.NEAREST - image = ImageOps.fit(image, size, Image.ANTIALIAS) + # create a thumbnail: will resize and keep ratios, then sharpen for better looking result + image.thumbnail(size, Image.ANTIALIAS) + sharpener = ImageEnhance.Sharpness(image.convert('RGBA')) + resized_image = sharpener.enhance(2.0) + # create a transparent image for background and paste the image on it + image = Image.new('RGBA', size, (255, 255, 255, 0)) + image.paste(resized_image, ((size[0] - resized_image.size[0]) / 2, (size[1] - resized_image.size[1]) / 2)) if image.mode not in ["1", "L", "P", "RGB", "RGBA"]: image = image.convert("RGB")