[MERGE] from trunk

bzr revid: xmo@openerp.com-20121010154605-u16f57fnck148ued
This commit is contained in:
Xavier Morel 2012-10-10 17:46:05 +02:00
commit 7a7876d4a8
12 changed files with 258 additions and 166 deletions

View File

@ -557,7 +557,9 @@ class ir_model_access(osv.osv):
model_name = model
# TransientModel records have no access rights, only an implicit access rule
if self.pool.get(model_name).is_transient():
if not self.pool.get(model_name):
_logger.error('Missing model %s' % (model_name, ))
elif self.pool.get(model_name).is_transient():
return True
# We check if a specific rule exists

View File

@ -265,7 +265,7 @@ class ir_ui_menu(osv.osv):
}
if menu.action and menu.action.type in ('ir.actions.act_window','ir.actions.client') and menu.action.res_model:
obj = self.pool.get(menu.action.res_model)
if obj._needaction:
if obj and obj._needaction:
if menu.action.type=='ir.actions.act_window':
dom = menu.action.domain and eval(menu.action.domain, {'uid': uid}) or []
else:

View File

@ -39,7 +39,7 @@
<field name="arch" type="xml">
<search string="Search modules">
<field name="name" filter_domain="['|', '|', ('summary', 'ilike', self), ('shortdesc', 'ilike', self), ('name',
'ilike', self)]"/>
'ilike', self)]" string="Module"/>
<filter name="app" icon="terp-check" string="Apps" domain="[('application', '=', 1)]"/>
<filter name="extra" icon="terp-check" string="Extra" domain="[('application', '=', 0)]"/>
<separator/>

View File

@ -106,8 +106,8 @@
<field name="zip" class="oe_inline" placeholder="ZIP"/>
<field name="city" class="oe_inline" placeholder="City"/>
</div>
<field name="state_id" placeholder="State" options='{"no_open": true}'/>
<field name="country_id" placeholder="Country" options='{"no_open": true}'/>
<field name="state_id" placeholder="State" options='{"no_open": True}'/>
<field name="country_id" placeholder="Country" options='{"no_open": True}'/>
</div>
</group>
<group name="bank" string="Information About the Bank">

View File

@ -46,10 +46,10 @@
<field name="street2"/>
<div>
<field name="city" placeholder="City" style="width: 40%%"/>
<field name="state_id" class="oe_no_button" placeholder="State" style="width: 24%%" options='{"no_open": true}'/>
<field name="state_id" class="oe_no_button" placeholder="State" style="width: 24%%" options='{"no_open": True}'/>
<field name="zip" placeholder="ZIP" style="width: 34%%"/>
</div>
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": true}' on_change="on_change_country(country_id)"/>
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": True}' on_change="on_change_country(country_id)"/>
</div>
<label for="rml_header1"/>
<div>

View File

@ -73,7 +73,7 @@
<group>
<field name="name"/>
<field name="code"/>
<field name="country_id" options='{"no_open": true}'/>
<field name="country_id" options='{"no_open": True}'/>
</group>
</form>
</field>

View File

@ -301,7 +301,7 @@ class res_partner(osv.osv, format_address):
def onchange_type(self, cr, uid, ids, is_company, context=None):
# get value as for an onchange on the image
value = tools.image_get_resized_images(self._get_default_image(cr, uid, is_company, context), return_big=True)
value = tools.image_get_resized_images(self._get_default_image(cr, uid, is_company, context), return_big=True, return_medium=False, return_small=False)
value['title'] = False
if is_company:
value['parent_id'] = False

View File

@ -161,10 +161,10 @@
<field name="street2"/>
<div class="address_format">
<field name="city" placeholder="City" style="width: 40%%"/>
<field name="state_id" class="oe_no_button" placeholder="State" style="width: 37%%" options='{"no_open": true}'/>
<field name="state_id" class="oe_no_button" placeholder="State" style="width: 37%%" options='{"no_open": True}'/>
<field name="zip" placeholder="ZIP" style="width: 20%%"/>
</div>
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": true}'/>
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": True}'/>
</div>
<field name="website" widget="url" placeholder="e.g. www.openerp.com"/>
</group>
@ -177,7 +177,7 @@
<field name="email" widget="email"/>
<field name="title" domain="[('domain', '=', 'contact')]"
groups="base.group_no_one"
options='{"no_open": true}' attrs="{'invisible': [('is_company','=', True)]}" />
options='{"no_open": True}' attrs="{'invisible': [('is_company','=', True)]}" />
</group>
</group>

View File

@ -341,14 +341,12 @@ class res_users(osv.osv):
def copy(self, cr, uid, id, default=None, context=None):
user2copy = self.read(cr, uid, [id], ['login','name'])[0]
if default is None:
default = {}
copy_pattern = _("%s (copy)")
copydef = dict(login=(copy_pattern % user2copy['login']),
name=(copy_pattern % user2copy['name']),
)
copydef.update(default)
return super(res_users, self).copy(cr, uid, id, copydef, context)
default = dict(default or {})
if ('name' not in default) and ('partner_id' not in default):
default['name'] = _("%s (copy)") % user2copy['name']
if 'login' not in default:
default['login'] = _("%s (copy)") % user2copy['login']
return super(res_users, self).copy(cr, uid, id, default, context)
def context_get(self, cr, uid, context=None):
user = self.browse(cr, SUPERUSER_ID, uid, context)

View File

@ -52,6 +52,7 @@ import pickle
import re
import simplejson
import time
import traceback
import types
import psycopg2
@ -65,6 +66,7 @@ import openerp.tools as tools
from openerp.tools.config import config
from openerp.tools.misc import CountingStream
from openerp.tools.safe_eval import safe_eval as eval
from ast import literal_eval
from openerp.tools.translate import _
from openerp import SUPERUSER_ID
from query import Query
@ -377,6 +379,8 @@ class browse_record(object):
else:
error_msg = "Field '%s' does not exist in object '%s'" % (name, self)
self.__logger.warning(error_msg)
if self.__logger.isEnabledFor(logging.DEBUG):
self.__logger.debug(''.join(traceback.format_stack()))
raise KeyError(error_msg)
# if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
@ -1808,7 +1812,13 @@ class BaseModel(object):
field = model_fields.get(node.get('name'))
if field:
transfer_field_to_modifiers(field, modifiers)
#evaluate the options as python code, but send it as json to the client
if node.get('options'):
try:
node.set('options', simplejson.dumps(literal_eval(node.get('options'))))
except Exception, e:
_logger.exception('Invalid `options´ attribute, should be a valid python expression: %r', node.get('options'))
raise except_orm('Invalid options', 'Invalid options: %r %s' % (node.get('options'), e))
elif node.tag in ('form', 'tree'):
result = self.view_header_get(cr, user, False, node.tag, context)
@ -3043,14 +3053,14 @@ class BaseModel(object):
('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
]
if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
if f_pg_type == 'varchar' and f._type == 'char' and ((f.size is None and f_pg_size) or f_pg_size < f.size):
cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, pg_varchar(f.size)))
cr.execute('UPDATE "%s" SET "%s"=temp_change_size::%s' % (self._table, k, pg_varchar(f.size)))
cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
cr.commit()
_schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
self._table, k, f_pg_size, f.size)
self._table, k, f_pg_size or 'unlimited', f.size or 'unlimited')
for c in casts:
if (f_pg_type==c[0]) and (f._type==c[1]):
if f_pg_type != f_obj_type:
@ -3283,8 +3293,7 @@ class BaseModel(object):
# attlen is the number of bytes necessary to represent the type when
# the type has a fixed size. If the type has a varying size attlen is
# -1 and atttypmod is the size limit + 4, or -1 if there is no limit.
# Thus the query can return a negative size for a unlimited varchar.
cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \
cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN (CASE WHEN a.atttypmod=-1 THEN 0 ELSE a.atttypmod-4 END) ELSE a.attlen END as size " \
"FROM pg_class c,pg_attribute a,pg_type t " \
"WHERE c.relname=%s " \
"AND c.oid=a.attrelid " \
@ -4823,56 +4832,45 @@ class BaseModel(object):
else:
raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
fields = self.fields_get(cr, uid, context=context)
for f in fields:
ftype = fields[f]['type']
if self._log_access and f in LOG_ACCESS_COLUMNS:
del data[f]
# build a black list of fields that should not be copied
blacklist = set(MAGIC_COLUMNS + ['parent_left', 'parent_right'])
def blacklist_given_fields(obj):
# blacklist the fields that are given by inheritance
for other, field_to_other in obj._inherits.items():
blacklist.add(field_to_other)
if field_to_other in default:
# all the fields of 'other' are given by the record: default[field_to_other],
# except the ones redefined in self
blacklist.update(set(self.pool.get(other)._all_columns) - set(self._columns))
else:
blacklist_given_fields(self.pool.get(other))
blacklist_given_fields(self)
res = dict(default)
for f, colinfo in self._all_columns.items():
field = colinfo.column
if f in default:
data[f] = default[f]
elif 'function' in fields[f]:
del data[f]
elif ftype == 'many2one':
try:
data[f] = data[f] and data[f][0]
except:
pass
elif ftype == 'one2many':
res = []
rel = self.pool.get(fields[f]['relation'])
if data[f]:
# duplicate following the order of the ids
# because we'll rely on it later for copying
# translations in copy_translation()!
data[f].sort()
for rel_id in data[f]:
# the lines are first duplicated using the wrong (old)
# parent but then are reassigned to the correct one thanks
# to the (0, 0, ...)
d = rel.copy_data(cr, uid, rel_id, context=context)
if d:
res.append((0, 0, d))
data[f] = res
elif ftype == 'many2many':
data[f] = [(6, 0, data[f])]
elif f in blacklist:
pass
elif isinstance(field, fields.function):
pass
elif field._type == 'many2one':
res[f] = data[f] and data[f][0]
elif field._type == 'one2many':
other = self.pool.get(field._obj)
# duplicate following the order of the ids because we'll rely on
# it later for copying translations in copy_translation()!
lines = [other.copy_data(cr, uid, line_id, context=context) for line_id in sorted(data[f])]
# the lines are duplicated using the wrong (old) parent, but then
# are reassigned to the correct one thanks to the (0, 0, ...)
res[f] = [(0, 0, line) for line in lines if line]
elif field._type == 'many2many':
res[f] = [(6, 0, data[f])]
else:
res[f] = data[f]
del data['id']
# make sure we don't break the current parent_store structure and
# force a clean recompute!
for parent_column in ['parent_left', 'parent_right']:
data.pop(parent_column, None)
# Remove _inherits field's from data recursively, missing parents will
# be created by create() (so that copy() copy everything).
def remove_ids(inherits_dict):
for parent_table in inherits_dict:
del data[inherits_dict[parent_table]]
remove_ids(self.pool.get(parent_table)._inherits)
remove_ids(self._inherits)
return data
return res
def copy_translations(self, cr, uid, old_id, new_id, context=None):
if context is None:

View File

@ -6,6 +6,85 @@ import common
UID = common.ADMIN_USER_ID
DB = common.DB
class TestInherits(common.TransactionCase):
""" test the behavior of the orm for models that use _inherits;
specifically: res.users, that inherits from res.partner
"""
def setUp(self):
super(TestInherits, self).setUp()
self.partner = self.registry('res.partner')
self.user = self.registry('res.users')
def test_create(self):
""" creating a user should automatically create a new partner """
partners_before = self.partner.search(self.cr, UID, [])
foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
foo = self.user.browse(self.cr, UID, foo_id)
self.assertNotIn(foo.partner_id.id, partners_before)
def test_create_with_ancestor(self):
""" creating a user with a specific 'partner_id' should not create a new partner """
par_id = self.partner.create(self.cr, UID, {'name': 'Foo'})
partners_before = self.partner.search(self.cr, UID, [])
foo_id = self.user.create(self.cr, UID, {'partner_id': par_id, 'login': 'foo', 'password': 'foo'})
partners_after = self.partner.search(self.cr, UID, [])
self.assertEqual(set(partners_before), set(partners_after))
foo = self.user.browse(self.cr, UID, foo_id)
self.assertEqual(foo.name, 'Foo')
self.assertEqual(foo.partner_id.id, par_id)
def test_read(self):
""" inherited fields should be read without any indirection """
foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
foo_values, = self.user.read(self.cr, UID, [foo_id])
partner_id = foo_values['partner_id'][0]
partner_values, = self.partner.read(self.cr, UID, [partner_id])
self.assertEqual(foo_values['name'], partner_values['name'])
foo = self.user.browse(self.cr, UID, foo_id)
self.assertEqual(foo.name, foo.partner_id.name)
def test_copy(self):
""" copying a user should automatically copy its partner, too """
foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
foo_before, = self.user.read(self.cr, UID, [foo_id])
bar_id = self.user.copy(self.cr, UID, foo_id, {'login': 'bar', 'password': 'bar'})
foo_after, = self.user.read(self.cr, UID, [foo_id])
self.assertEqual(foo_before, foo_after)
foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
self.assertEqual(bar.login, 'bar')
self.assertNotEqual(foo.id, bar.id)
self.assertNotEqual(foo.partner_id.id, bar.partner_id.id)
def test_copy_with_ancestor(self):
""" copying a user with 'parent_id' in defaults should not duplicate the partner """
foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
par_id = self.partner.create(self.cr, UID, {'name': 'Bar'})
foo_before, = self.user.read(self.cr, UID, [foo_id])
partners_before = self.partner.search(self.cr, UID, [])
bar_id = self.user.copy(self.cr, UID, foo_id, {'partner_id': par_id, 'login': 'bar'})
foo_after, = self.user.read(self.cr, UID, [foo_id])
partners_after = self.partner.search(self.cr, UID, [])
self.assertEqual(foo_before, foo_after)
self.assertEqual(set(partners_before), set(partners_after))
foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
self.assertNotEqual(foo.id, bar.id)
self.assertEqual(bar.partner_id.id, par_id)
self.assertEqual(bar.login, 'bar', "login is given from copy parameters")
self.assertEqual(bar.password, foo.password, "password is given from original record")
self.assertEqual(bar.name, 'Bar', "name is given from specific partner")
CREATE = lambda values: (0, False, values)
UPDATE = lambda id, values: (1, id, values)
DELETE = lambda id: (2, id, False)
@ -19,6 +98,7 @@ def sorted_by_id(list_of_dicts):
return sorted(list_of_dicts, key=lambda d: d.get('id'))
class TestO2MSerialization(common.TransactionCase):
""" test the orm method 'write' on one2many fields """
def setUp(self):
super(TestO2MSerialization, self).setUp()

View File

@ -313,13 +313,13 @@ class YamlInterpreter(object):
#context = self.get_context(record, self.eval_context)
#TOFIX: record.context like {'withoutemployee':True} should pass from self.eval_context. example: test_project.yml in project module
context = record.context
view_info = False
if view_id:
varg = view_id
if view_id is True: varg = False
view = model.fields_view_get(self.cr, SUPERUSER_ID, varg, 'form', context)
view_id = etree.fromstring(view['arch'].encode('utf-8'))
view_info = model.fields_view_get(self.cr, SUPERUSER_ID, varg, 'form', context)
record_dict = self._create_record(model, fields, view_id, default=default)
record_dict = self._create_record(model, fields, view_info, default=default)
_logger.debug("RECORD_DICT %s" % record_dict)
id = self.pool.get('ir.model.data')._update(self.cr, SUPERUSER_ID, record.model, \
self.module, record_dict, record.id, noupdate=self.isnoupdate(record), mode=self.mode, context=context)
@ -327,16 +327,18 @@ class YamlInterpreter(object):
if config.get('import_partial'):
self.cr.commit()
def _create_record(self, model, fields, view=False, parent={}, default=True):
if view is not False:
defaults = default and model._add_missing_default_values(self.cr, SUPERUSER_ID, {}, context=self.context) or {}
fg = model.fields_get(self.cr, SUPERUSER_ID, context=self.context)
else:
defaults = {}
fg = {}
record_dict = {}
fields = fields or {}
def _create_record(self, model, fields, view_info=False, parent={}, default=True):
"""This function processes the !record tag in yalm files. It simulates the record creation through an xml
view (either specified on the !record tag or the default one for this object), including the calls to
on_change() functions.
:param model: model instance
:param fields: dictonary mapping the field names and their values
:param view_info: result of fields_view_get() called on the object
:param parent: dictionary containing the values already computed for the parent, in case of one2many fields
:param default: if True, the default values must be processed too or not
:return: dictionary mapping the field names and their values, ready to use when calling the create() function
:rtype: dict
"""
def process_val(key, val):
if fg[key]['type']=='many2one':
if type(val) in (tuple,list):
@ -348,30 +350,41 @@ class YamlInterpreter(object):
val = map(lambda x: (0,0,x), val)
return val
view = view_info and etree.fromstring(view_info['arch'].encode('utf-8')) or False
fields = fields or {}
if view is not False:
fg = view_info['fields']
# gather the default values on the object. (Can't use `fields´ as parameter instead of {} because we may
# have references like `base.main_company´ in the yaml file and it's not compatible with the function)
defaults = default and model._add_missing_default_values(self.cr, SUPERUSER_ID, {}, context=self.context) or {}
# copy the default values in record_dict, only if they are in the view (because that's what the client does)
# the other default values will be added later on by the create().
record_dict = dict([(key, val) for key, val in defaults.items() if key in fg])
# Process all on_change calls
nodes = (view is not False) and [view] or []
nodes = [view]
while nodes:
el = nodes.pop(0)
if el.tag=='field':
field_name = el.attrib['name']
assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name)
if field_name in fields:
view2 = None
# if the form view is not inline, we call fields_view_get
one2many_form_view = None
if (view is not False) and (fg[field_name]['type']=='one2many'):
view2 = view.find("field[@name='%s']/form"%(field_name,))
if not view2:
view2 = self.pool.get(fg[field_name]['relation']).fields_view_get(self.cr, SUPERUSER_ID, False, 'form', self.context)
view2 = etree.fromstring(view2['arch'].encode('utf-8'))
# for one2many fields, we want to eval them using the inline form view defined on the parent
one2many_form_view = view_info['fields'][field_name]['views'].get('form')
# if the form view is not defined inline, we call fields_view_get()
if not one2many_form_view:
one2many_form_view = self.pool.get(fg[field_name]['relation']).fields_view_get(self.cr, SUPERUSER_ID, False, 'form', self.context)
field_value = self._eval_field(model, field_name, fields[field_name], view2, parent=record_dict, default=default)
field_value = self._eval_field(model, field_name, fields[field_name], one2many_form_view or view_info, parent=record_dict, default=default)
record_dict[field_name] = field_value
#if (field_name in defaults) and defaults[field_name] == field_value:
# print '*** You can remove these lines:', field_name, field_value
elif (field_name in defaults):
if (field_name not in record_dict):
record_dict[field_name] = process_val(field_name, defaults[field_name])
else:
#if field_name has a default value or a value is given in the yaml file, we must call its on_change()
elif field_name not in defaults:
continue
if not el.attrib.get('on_change', False):
@ -388,7 +401,7 @@ class YamlInterpreter(object):
ctx = record_dict.copy()
ctx['context'] = self.context
ctx['uid'] = 1
ctx['uid'] = SUPERUSER_ID
ctx['parent'] = parent2(parent)
for a in fg:
if a not in ctx:
@ -398,13 +411,14 @@ class YamlInterpreter(object):
args = map(lambda x: eval(x, ctx), match.group(2).split(','))
result = getattr(model, match.group(1))(self.cr, SUPERUSER_ID, [], *args)
for key, val in (result or {}).get('value', {}).items():
if key not in fields:
assert key in fg, "The returning field '%s' from your on_change call '%s' does not exist on the object '%s'" % (key, match.group(1), model._name)
assert key in fg, "The returning field '%s' from your on_change call '%s' does not exist either on the object '%s', either in the view '%s' used for the creation" % (key, match.group(1), model._name, view_info['name'])
record_dict[key] = process_val(key, val)
#if (key in fields) and record_dict[key] == process_val(key, val):
# print '*** You can remove these lines:', key, val
else:
nodes = list(el) + nodes
else:
record_dict = {}
for field_name, expression in fields.items():
if field_name in record_dict:
@ -440,7 +454,7 @@ class YamlInterpreter(object):
def process_eval(self, node):
return eval(node.expression, self.eval_context)
def _eval_field(self, model, field_name, expression, view=False, parent={}, default=True):
def _eval_field(self, model, field_name, expression, view_info=False, parent={}, default=True):
# TODO this should be refactored as something like model.get_field() in bin/osv
if field_name in model._columns:
column = model._columns[field_name]
@ -461,7 +475,7 @@ class YamlInterpreter(object):
value = self.get_id(expression)
elif column._type == "one2many":
other_model = self.get_model(column._obj)
value = [(0, 0, self._create_record(other_model, fields, view, parent, default=default)) for fields in expression]
value = [(0, 0, self._create_record(other_model, fields, view_info, parent, default=default)) for fields in expression]
elif column._type == "many2many":
ids = [self.get_id(xml_id) for xml_id in expression]
value = [(6, 0, ids)]