[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
@ -375,8 +377,10 @@ class browse_record(object):
else:
return attr
else:
error_msg = "Field '%s' does not exist in object '%s'" % (name, self)
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
@ -713,7 +717,7 @@ class BaseModel(object):
def log(self, cr, uid, id, message, secondary=False, context=None):
return _logger.warning("log() is deprecated. Please use OpenChatter notification system instead of the res.log mechanism.")
def view_init(self, cr, uid, fields_list, context=None):
"""Override this method to do specific things when a view on the object is opened."""
pass
@ -903,7 +907,7 @@ class BaseModel(object):
# If new class defines a constraint with
# same function name, we let it override
# the old one.
new[c2] = c
exist = True
break
@ -1672,7 +1676,7 @@ class BaseModel(object):
def user_has_groups(self, cr, uid, groups, context=None):
"""Return true if the user is at least member of one of the groups
in groups_str. Typically used to resolve ``groups`` attribute
in view and model definitions.
in view and model definitions.
:param str groups: comma-separated list of fully-qualified group
external IDs, e.g.: ``base.group_user,base.group_system``
@ -1715,7 +1719,7 @@ class BaseModel(object):
the field should be completely removed from the view, as it is
completely unavailable for non-members
:return: True if field should be included in the result of fields_view_get
:return: True if field should be included in the result of fields_view_get
"""
if node.tag == 'field' and node.get('name') in self._all_columns:
column = self._all_columns[node.get('name')].column
@ -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)
@ -1990,7 +2000,7 @@ class BaseModel(object):
def _get_default_calendar_view(self, cr, user, context=None):
""" Generates a default calendar view by trying to infer
calendar fields from a number of pre-set attribute names
:param cr: database cursor
:param int user: user id
:param dict context: connection context
@ -2069,7 +2079,7 @@ class BaseModel(object):
def raise_view_error(error_msg, child_view_id):
view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
error_msg = error_msg % {'parent_xml_id': view.xml_id}
error_msg = error_msg % {'parent_xml_id': view.xml_id}
raise AttributeError("View definition error for inherited view '%s' on model '%s': %s"
% (child_view.xml_id, self._name, error_msg))
@ -2105,7 +2115,7 @@ class BaseModel(object):
if all(node.get(attr) == spec.get(attr) \
for attr in spec.attrib
if attr not in ('position','version')):
# Version spec should match parent's root element's version
# Version spec should match parent's root element's version
if spec.get('version') and spec.get('version') != source.get('version'):
return None
return node
@ -2179,7 +2189,7 @@ class BaseModel(object):
raise_view_error("Mismatching view API version for element '%s': %r vs %r in parent view '%%(parent_xml_id)s'" % \
(tag, spec.get('version'), source.get('version')), inherit_id)
raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
return source
def apply_view_inheritance(cr, user, source, inherit_id):
@ -2394,7 +2404,7 @@ class BaseModel(object):
or ``'='``.
:param int limit: optional max number of records to return
:rtype: list
:return: list of pairs ``(id,text_repr)`` for all matching records.
:return: list of pairs ``(id,text_repr)`` for all matching records.
"""
return self._name_search(cr, user, name, args, operator, context, limit)
@ -2524,7 +2534,7 @@ class BaseModel(object):
# This is useful to implement kanban views for instance, where all columns
# should be displayed even if they don't contain any record.
# Grab the list of all groups that should be displayed, including all present groups
# Grab the list of all groups that should be displayed, including all present groups
present_group_ids = [x[groupby][0] for x in read_group_result if x[groupby]]
all_groups,folded = self._group_by_full[groupby](self, cr, uid, present_group_ids, domain,
read_group_order=read_group_order,
@ -2900,28 +2910,28 @@ class BaseModel(object):
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
# 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
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
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'",
@ -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 " \
@ -3294,7 +3303,7 @@ class BaseModel(object):
def _o2m_raise_on_missing_reference(self, cr, f):
# TODO this check should be a method on fields.one2many.
other = self.pool.get(f._obj)
if other:
# TODO the condition could use fields_get_keys().
@ -3811,7 +3820,7 @@ class BaseModel(object):
the length of `ids`, and raise an appropriate exception if it does not.
"""
if cr.rowcount != len(ids):
# Attempt to distinguish record rule restriction vs deleted records,
# Attempt to distinguish record rule restriction vs deleted records,
# to provide a more specific error message
cr.execute('SELECT id FROM ' + self._table + ' WHERE id IN %s', (tuple(ids),))
if cr.rowcount != len(ids):
@ -3866,13 +3875,13 @@ class BaseModel(object):
self._check_record_rules_result_count(cr, uid, sub_ids, operation, context=context)
def _workflow_trigger(self, cr, uid, ids, trigger, context=None):
"""Call given workflow trigger as a result of a CRUD operation"""
"""Call given workflow trigger as a result of a CRUD operation"""
wf_service = netsvc.LocalService("workflow")
for res_id in ids:
getattr(wf_service, trigger)(uid, self._name, res_id, cr)
def _workflow_signal(self, cr, uid, ids, signal, context=None):
"""Send given workflow signal"""
"""Send given workflow signal"""
wf_service = netsvc.LocalService("workflow")
for res_id in ids:
wf_service.trg_validate(uid, self._name, res_id, signal, cr)
@ -3903,7 +3912,7 @@ class BaseModel(object):
self.check_access_rights(cr, uid, 'unlink')
ir_property = self.pool.get('ir.property')
# Check if the records are used as default properties.
domain = [('res_id', '=', False),
('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
@ -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])]
pass
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:
@ -5031,7 +5029,7 @@ class BaseModel(object):
:return: map of ids to their fully qualified XML ID,
defaulting to an empty string when there's none
(to be usable as a function field),
(to be usable as a function field),
e.g.::
{ 'id': 'module.ext_id',

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,63 +350,75 @@ class YamlInterpreter(object):
val = map(lambda x: (0,0,x), val)
return val
# Process all on_change calls
nodes = (view is not False) and [view] or []
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
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'))
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 {}
field_value = self._eval_field(model, field_name, fields[field_name], view2, 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:
continue
# 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])
if not el.attrib.get('on_change', False):
continue
match = re.match("([a-z_1-9A-Z]+)\((.*)\)", el.attrib['on_change'])
assert match, "Unable to parse the on_change '%s'!" % (el.attrib['on_change'], )
# Process all on_change calls
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:
one2many_form_view = None
if (view is not False) and (fg[field_name]['type']=='one2many'):
# 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)
# creating the context
class parent2(object):
def __init__(self, d):
self.d = d
def __getattr__(self, name):
return self.d.get(name, False)
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
ctx = record_dict.copy()
ctx['context'] = self.context
ctx['uid'] = 1
ctx['parent'] = parent2(parent)
for a in fg:
if a not in ctx:
ctx[a]=process_val(a, defaults.get(a, False))
#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
# Evaluation args
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)
if not el.attrib.get('on_change', False):
continue
match = re.match("([a-z_1-9A-Z]+)\((.*)\)", el.attrib['on_change'])
assert match, "Unable to parse the on_change '%s'!" % (el.attrib['on_change'], )
# creating the context
class parent2(object):
def __init__(self, d):
self.d = d
def __getattr__(self, name):
return self.d.get(name, False)
ctx = record_dict.copy()
ctx['context'] = self.context
ctx['uid'] = SUPERUSER_ID
ctx['parent'] = parent2(parent)
for a in fg:
if a not in ctx:
ctx[a]=process_val(a, defaults.get(a, False))
# Evaluation args
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():
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:
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)]