[MERGE] from trunk
bzr revid: xmo@openerp.com-20121010154605-u16f57fnck148ued
This commit is contained in:
commit
7a7876d4a8
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Reference in New Issue