merge upstream

bzr revid: chs@openerp.com-20121107112830-fi0xu1poqt5cbb8u
This commit is contained in:
Christophe Simonis 2012-11-07 12:28:30 +01:00
commit 9bb5e47baf
18 changed files with 341 additions and 174 deletions

26
README
View File

@ -6,17 +6,9 @@ Customer Relationship Management software. More info at:
http://www.openerp.com
Installation on Debian Ubuntu
Installation on Debian/Ubuntu
-----------------------------
Download the deb file and type:
$ sudo dpkg -i <openerp-deb-filename>
$ sudo apt-get install install -f
Installation on Debian Ubuntu from nightly build
------------------------------------------------
Add the the apt repository
deb http://nightly.openerp.com/6.1/deb/ ./
@ -26,6 +18,11 @@ in your source.list and type:
$ sudo apt-get update
$ sudo apt-get install openerp
Or download the deb file and type:
$ sudo dpkg -i <openerp-deb-filename>
$ sudo apt-get install install -f
Installation on RedHat, Fedora, CentOS
--------------------------------------
@ -42,6 +39,8 @@ Install the openerp rpm
Installation on Windows
-----------------------
Check the notes in setup.py
Installation on MacOSX
-----------------------
@ -54,14 +53,7 @@ default master password is "admin".
Detailed System Requirements
----------------------------
You need the following software installed:
postgresql-client, python-dateutil, python-feedparser, python-gdata,
python-ldap, python-libxslt1, python-lxml, python-mako, python-openid,
python-psycopg2, python-pybabel, python-pychart, python-pydot,
python-pyparsing, python-reportlab, python-simplejson, python-tz,
python-vatnumber, python-vobject, python-webdav, python-werkzeug, python-xlwt,
python-yaml, python-zsi
The dependencies are listed in setup.py
For Luxembourg localization, you also need:

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-11-05 04:40+0000\n"
"X-Launchpad-Export-Date: 2012-11-06 04:50+0000\n"
"X-Generator: Launchpad (build 16232)\n"
#. module: base

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import collections
import datetime
import functools
import operator
@ -31,9 +32,70 @@ REPLACE_WITH = lambda ids: (6, False, ids)
class ConversionNotFound(ValueError): pass
class ColumnWrapper(object):
def __init__(self, column, cr, uid, pool, fromtype, context=None):
self._converter = None
self._column = column
if column._obj:
self._pool = pool
self._converter_args = {
'cr': cr,
'uid': uid,
'model': pool[column._obj],
'fromtype': fromtype,
'context': context
}
@property
def converter(self):
if not self._converter:
self._converter = self._pool['ir.fields.converter'].for_model(
**self._converter_args)
return self._converter
def __getattr__(self, item):
return getattr(self._column, item)
class ir_fields_converter(orm.Model):
_name = 'ir.fields.converter'
def for_model(self, cr, uid, model, fromtype=str, context=None):
""" Returns a converter object for the model. A converter is a
callable taking a record-ish (a dictionary representing an openerp
record with values of typetag ``fromtype``) and returning a converted
records matching what :meth:`openerp.osv.orm.Model.write` expects.
:param model: :class:`openerp.osv.orm.Model` for the conversion base
:returns: a converter callable
:rtype: (record: dict, logger: (field, error) -> None) -> dict
"""
columns = dict(
(k, ColumnWrapper(v.column, cr, uid, self.pool, fromtype, context))
for k, v in model._all_columns.iteritems())
converters = dict(
(k, self.to_field(cr, uid, model, column, fromtype, context))
for k, column in columns.iteritems())
def fn(record, log):
converted = {}
for field, value in record.iteritems():
if field in (None, 'id', '.id'): continue
if not value:
converted[field] = False
continue
try:
converted[field], ws = converters[field](value)
for w in ws:
if isinstance(w, basestring):
# wrap warning string in an ImportWarning for
# uniform handling
w = ImportWarning(w)
log(field, w)
except ValueError, e:
log(field, e)
return converted
return fn
def to_field(self, cr, uid, model, column, fromtype=str, context=None):
""" Fetches a converter for the provided column object, from the
specified type.
@ -343,6 +405,10 @@ class ir_fields_converter(orm.Model):
# [{subfield:ref1},{subfield:ref2},{subfield:ref3}]
records = ({subfield:item} for item in record[subfield].split(','))
def log(_, e):
if not isinstance(e, Warning):
raise e
warnings.append(e)
for record in records:
id = None
refs = only_ref_fields(record)
@ -355,7 +421,7 @@ class ir_fields_converter(orm.Model):
cr, uid, model, column, subfield, reference, context=context)
warnings.extend(w2)
writable = exclude_ref_fields(record)
writable = column.converter(exclude_ref_fields(record), log)
if id:
commands.append(LINK_TO(id))
commands.append(UPDATE(id, writable))

View File

@ -24,6 +24,7 @@ import logging
import openerp.modules
from openerp.osv import fields, osv
from tools.translate import _
_logger = logging.getLogger(__name__)
@ -336,6 +337,8 @@ class ir_translation(osv.osv):
trans_model = self.pool.get(model)
domain = ['&', ('res_id', '=', id), ('name', '=like', model + ',%')]
langs_ids = self.pool.get('res.lang').search(cr, uid, [('code', '!=', 'en_US')], context=context)
if not langs_ids:
raise osv.except_osv(_('Error'), _("Translation features are unavailable until you install an extra OpenERP translation."))
langs = [lg.code for lg in self.pool.get('res.lang').browse(cr, uid, langs_ids, context=context)]
main_lang = 'en_US'
translatable_fields = []

View File

@ -264,10 +264,15 @@ class res_partner(osv.osv, format_address):
return False
def _get_default_image(self, cr, uid, is_company, context=None, colorize=False):
if is_company:
image = open(openerp.modules.get_module_resource('base', 'static/src/img', 'company_image.png')).read()
else:
image = tools.image_colorize(open(openerp.modules.get_module_resource('base', 'static/src/img', 'avatar.png')).read())
img_path = openerp.modules.get_module_resource('base', 'static/src/img',
('company_image.png' if is_company else 'avatar.png'))
with open(img_path, 'rb') as f:
image = f.read()
# colorize user avatars
if not is_company:
image = tools.image_colorize(image)
return tools.image_resize_image_big(image.encode('base64'))
def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):

View File

@ -38,7 +38,6 @@ import openerp.osv as osv
import openerp.pooler as pooler
import openerp.release as release
import openerp.tools as tools
import openerp.tools.assertion_report as assertion_report
from openerp import SUPERUSER_ID
from openerp import SUPERUSER_ID
@ -285,7 +284,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# processed_modules: for cleanup step after install
# loaded_modules: to avoid double loading
report = assertion_report.assertion_report()
report = pool._assertion_report
loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report)
if tools.config['load_language']:

View File

@ -32,6 +32,7 @@ import openerp.cron
import openerp.tools
import openerp.modules.db
import openerp.tools.config
from openerp.tools import assertion_report
_logger = logging.getLogger(__name__)
@ -49,6 +50,7 @@ class Registry(object):
self._store_function = {}
self._init = True
self._init_parent = {}
self._assertion_report = assertion_report.assertion_report()
# modules fully loaded (maintained during init phase by `loading` module)
self._init_modules = set()

View File

@ -1152,96 +1152,51 @@ class related(function):
"""
def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
self._field_get2(cr, uid, obj, context)
i = len(self._arg)-1
sarg = name
while i>0:
if type(sarg) in [type([]), type( (1,) )]:
where = [(self._arg[i], 'in', sarg)]
else:
where = [(self._arg[i], '=', sarg)]
if domain:
where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
domain = []
sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
i -= 1
return [(self._arg[0], 'in', sarg)]
# assume self._arg = ('foo', 'bar', 'baz')
# domain = [(name, op, val)] => search [('foo.bar.baz', op, val)]
field = '.'.join(self._arg)
return map(lambda x: (field, x[1], x[2]), domain)
def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
self._field_get2(cr, uid, obj, context=context)
if type(ids) != type([]):
ids=[ids]
objlst = obj.browse(cr, uid, ids)
for data in objlst:
t_id = data.id
t_data = data
for i in range(len(self.arg)):
if not t_data: break
field_detail = self._relations[i]
if not t_data[self.arg[i]]:
if self._type not in ('one2many', 'many2many'):
t_id = t_data['id']
t_data = False
elif field_detail['type'] in ('one2many', 'many2many'):
if self._type != "many2one":
t_id = t_data.id
t_data = t_data[self.arg[i]][0]
else:
t_data = False
else:
t_id = t_data['id']
t_data = t_data[self.arg[i]]
else:
model = obj.pool.get(self._relations[-1]['object'])
model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
if isinstance(ids, (int, long)):
ids = [ids]
for record in obj.browse(cr, uid, ids, context=context):
# traverse all fields except the last one
for field in self.arg[:-1]:
record = record[field] or False
if not record:
break
elif isinstance(record, list):
# record is the result of a one2many or many2many field
record = record[0]
if record:
# write on the last field
record.write({self.arg[-1]: values})
def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
self._field_get2(cr, uid, obj, context)
if not ids: return {}
relation = obj._name
if self._type in ('one2many', 'many2many'):
res = dict([(i, []) for i in ids])
else:
res = {}.fromkeys(ids, False)
objlst = obj.browse(cr, SUPERUSER_ID, ids, context=context)
for data in objlst:
if not data:
continue
t_data = data
relation = obj._name
for i in range(len(self.arg)):
field_detail = self._relations[i]
relation = field_detail['object']
try:
if not t_data[self.arg[i]]:
t_data = False
break
except:
t_data = False
res = {}
for record in obj.browse(cr, SUPERUSER_ID, ids, context=context):
value = record
for field in self.arg:
if isinstance(value, list):
value = value[0]
value = value[field] or False
if not value:
break
if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
t_data = t_data[self.arg[i]][0]
elif t_data:
t_data = t_data[self.arg[i]]
if type(t_data) == type(objlst[0]):
res[data.id] = t_data.id
elif t_data:
res[data.id] = t_data
if self._type=='many2one':
ids = filter(None, res.values())
if ids:
# name_get as root, as seeing the name of a related
# object depends on access right of source document,
# not target, so user may not have access.
ng = dict(obj.pool.get(self._obj).name_get(cr, SUPERUSER_ID, ids, context=context))
for r in res:
if res[r]:
res[r] = (res[r], ng[res[r]])
res[record.id] = value
if self._type == 'many2one':
# res[id] is a browse_record or False; convert it to (id, name) or False.
# Perform name_get as root, as seeing the name of a related object depends on
# access right of source document, not target, so user may not have access.
value_ids = list(set(value.id for value in res.itervalues() if value))
value_name = dict(obj.pool.get(self._obj).name_get(cr, SUPERUSER_ID, value_ids, context=context))
res = dict((id, value and (value.id, value_name[value.id])) for id, value in res.iteritems())
elif self._type in ('one2many', 'many2many'):
for r in res:
if res[r]:
res[r] = [x.id for x in res[r]]
# res[id] is a list of browse_record or False; convert it to a list of ids
res = dict((id, value and map(int, value) or []) for id, value in res.iteritems())
return res
def __init__(self, *arg, **args):
@ -1252,22 +1207,6 @@ class related(function):
# TODO: improve here to change self.store = {...} according to related objects
pass
def _field_get2(self, cr, uid, obj, context=None):
if self._relations:
return
result = []
obj_name = obj._name
for i in range(len(self._arg)):
f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
result.append({
'object': obj_name,
'type': f['type']
})
if f.get('relation',False):
obj_name = f['relation']
result[-1]['relation'] = f['relation']
self._relations = result
class sparse(function):

View File

@ -1352,9 +1352,11 @@ class BaseModel(object):
noupdate=noupdate, res_id=id, context=context))
cr.execute('RELEASE SAVEPOINT model_load_save')
except psycopg2.Warning, e:
_logger.exception('Failed to import record %s', record)
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
messages.append(dict(info, type='warning', message=str(e)))
except psycopg2.Error, e:
_logger.exception('Failed to import record %s', record)
# Failed to write, log to messages, rollback savepoint (to
# avoid broken transaction) and keep going
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
@ -1458,15 +1460,16 @@ class BaseModel(object):
field_names = dict(
(f, (Translation._get_source(cr, uid, self._name + ',' + f, 'field',
context.get('lang'))
or column.string or f))
or column.string))
for f, column in columns.iteritems())
converters = dict(
(k, Converter.to_field(cr, uid, self, column, context=context))
for k, column in columns.iteritems())
convert = Converter.for_model(cr, uid, self, context=context)
def _log(base, field, exception):
type = 'warning' if isinstance(exception, Warning) else 'error'
record = dict(base, field=field, type=type,
# logs the logical (not human-readable) field name for automated
# processing of response, but injects human readable in message
record = dict(base, type=type, field=field,
message=unicode(exception.args[0]) % base)
if len(exception.args) > 1 and exception.args[1]:
record.update(exception.args[1])
@ -1476,7 +1479,6 @@ class BaseModel(object):
for record, extras in stream:
dbid = False
xid = False
converted = {}
# name_get/name_create
if None in record: pass
# xid
@ -1497,27 +1499,8 @@ class BaseModel(object):
message=_(u"Unknown database identifier '%s'") % dbid))
dbid = False
for field, strvalue in record.iteritems():
if field in (None, 'id', '.id'): continue
if not strvalue:
converted[field] = False
continue
# In warnings and error messages, use translated string as
# field name
message_base = dict(
extras, record=stream.index, field=field_names[field])
try:
converted[field], ws = converters[field](strvalue)
for w in ws:
if isinstance(w, basestring):
# wrap warning string in an ImportWarning for
# uniform handling
w = ImportWarning(w)
_log(message_base, field, w)
except ValueError, e:
_log(message_base, field, e)
converted = convert(record, lambda field, err:\
_log(dict(extras, record=stream.index, field=field_names[field]), field, err))
yield dbid, xid, converted, dict(extras, record=stream.index)
@ -3878,10 +3861,12 @@ class BaseModel(object):
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 and return a dict mapping ids to workflow results"""
wf_service = netsvc.LocalService("workflow")
result = {}
for res_id in ids:
wf_service.trg_validate(uid, self._name, res_id, signal, cr)
result[res_id] = wf_service.trg_validate(uid, self._name, res_id, signal, cr)
return result
def unlink(self, cr, uid, ids, context=None):
"""

View File

@ -188,7 +188,8 @@ class object_proxy(object):
object = pooler.get_pool(cr.dbname).get(obj)
if not object:
raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj))
return object._workflow_signal(cr, uid, [args[0]], signal)
res_id = args[0]
return object._workflow_signal(cr, uid, [res_id], signal)[res_id]
@check
def exec_workflow(self, db, uid, obj, signal, *args):

View File

@ -34,7 +34,6 @@ import openerp.netsvc
import openerp.osv
import openerp.tools
import openerp.service.wsgi_server
import openerp.service.workers
#.apidoc title: RPC Services
@ -123,6 +122,7 @@ def stop_services():
logging.shutdown()
def start_services_workers():
import openerp.service.workers
openerp.multi_process = True
openerp.service.workers.Multicorn(openerp.service.wsgi_server.application).run()

View File

@ -3,20 +3,17 @@
# TODO rename class: Multicorn -> Arbiter ?
#-----------------------------------------------------------
import errno
try:
import fcntl
except ImportError:
fcntl = None
import fcntl
import logging
import os
import psutil
import random
import resource
import select
import socket
import time
import logging
import os
import signal
import socket
import sys
import time
import werkzeug.serving

View File

@ -9,7 +9,7 @@ See the :ref:`test-framework` section in the :ref:`features` list.
"""
from . import test_expression, test_html_sanitize, test_ir_sequence, test_orm,\
test_basecase, \
test_fields, test_basecase, \
test_view_validation, test_uninstall, test_misc, test_db_cursor
fast_suite = [
@ -21,6 +21,7 @@ checks = [
test_html_sanitize,
test_db_cursor,
test_orm,
test_fields,
test_basecase,
test_view_validation,
test_misc,

View File

@ -79,6 +79,7 @@ class One2ManyMultiple(orm.Model):
_name = 'export.one2many.multiple'
_columns = {
'parent_id': fields.many2one('export.one2many.recursive'),
'const': fields.integer(),
'child1': fields.one2many('export.one2many.child.1', 'parent_id'),
'child2': fields.one2many('export.one2many.child.2', 'parent_id'),
@ -135,3 +136,11 @@ class SelectionWithDefault(orm.Model):
'const': 4,
'value': 2,
}
class RecO2M(orm.Model):
_name = 'export.one2many.recursive'
_columns = {
'value': fields.integer(),
'child': fields.one2many('export.one2many.multiple', 'parent_id')
}

View File

@ -0,0 +1 @@
[["Wood y Wood Pecker", "", "Snow Street, 25", "Kainuu", "Finland", "Supplier", "1", "0", "1", ""], ["Roger Pecker", "Default", "Snow Street, 27", "Kainuu", "Finland", "Supplier", "1", "0", "0", "Wood y Wood Pecker"], ["Sharon Pecker", "Shipping", "Snow Street, 28", "Kainuu", "Finland", "Supplier", "1", "0", "0", "Wood y Wood Pecker"], ["Thomas Pecker", "Contact", "Snow Street, 27", "Kainuu", "Finland", "Supplier", "1", "0", "0", "Wood y Wood Pecker"], ["Norseman Roundabout", "", "Atonium Street, 45a", "Brussels", "Belgium", "Supplier", "1", "0", "1", ""], ["Yvan Holiday", "Invoice", "Atonium Street, 45b", "Brussels", "Belgium", "Supplier", "1", "0", "0", "Norseman Roundabout"], ["Jack Unsworth", "Contact", "Atonium Street, 45a", "Brussels", "Belgium", "Supplier", "1", "0", "0", "Norseman Roundabout"]]

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import json
import pkgutil
import unittest2
import openerp.modules.registry
import openerp
@ -246,7 +247,7 @@ class test_integer_field(ImporterCase):
-1, -42, -(2**31 - 1), -(2**31), -12345678
], values(self.read()))
@mute_logger('openerp.sql_db')
@mute_logger('openerp.sql_db', 'openerp.osv.orm')
def test_out_of_range(self):
result = self.import_(['value'], [[str(2**31)]])
self.assertIs(result['ids'], False)
@ -388,14 +389,14 @@ class test_unbound_string_field(ImporterCase):
class test_required_string_field(ImporterCase):
model_name = 'export.string.required'
@mute_logger('openerp.sql_db')
@mute_logger('openerp.sql_db', 'openerp.osv.orm')
def test_empty(self):
result = self.import_(['value'], [[]])
self.assertEqual(result['messages'], [message(
u"Missing required value for the field 'unknown'")])
self.assertIs(result['ids'], False)
@mute_logger('openerp.sql_db')
@mute_logger('openerp.sql_db', 'openerp.osv.orm')
def test_not_provided(self):
result = self.import_(['const'], [['12']])
self.assertEqual(result['messages'], [message(
@ -1007,6 +1008,46 @@ class test_realworld(common.TransactionCase):
self.assertFalse(result['messages'])
self.assertEqual(len(result['ids']), len(data))
def test_backlink(self):
data = json.loads(pkgutil.get_data(self.__module__, 'contacts.json'))
result = self.registry('res.partner').load(
self.cr, openerp.SUPERUSER_ID,
["name", "type", "street", "city", "country_id", "category_id",
"supplier", "customer", "is_company", "parent_id"],
data)
self.assertFalse(result['messages'])
self.assertEqual(len(result['ids']), len(data))
def test_recursive_o2m(self):
""" The content of the o2m field's dict needs to go through conversion
as it may be composed of convertables or other relational fields
"""
self.registry('ir.model.data').clear_caches()
Model = self.registry('export.one2many.recursive')
result = Model.load(self.cr, openerp.SUPERUSER_ID,
['value', 'child/const', 'child/child1/str', 'child/child2/value'],
[
['4', '42', 'foo', '55'],
['', '43', 'bar', '56'],
['', '', 'baz', ''],
['', '55', 'qux', '57'],
['5', '99', 'wheee', ''],
['', '98', '', '12'],
],
context=None)
self.assertFalse(result['messages'])
self.assertEqual(len(result['ids']), 2)
b = Model.browse(self.cr, openerp.SUPERUSER_ID, result['ids'], context=None)
self.assertEqual((b[0].value, b[1].value), (4, 5))
self.assertEqual([child.str for child in b[0].child[1].child1],
['bar', 'baz'])
self.assertFalse(len(b[1].child[1].child1))
self.assertEqual([child.value for child in b[1].child[1].child2],
[12])
class test_date(ImporterCase):
model_name = 'export.date'

View File

@ -0,0 +1,112 @@
#
# test cases for fields access, etc.
#
import unittest2
import common
import openerp
from openerp.osv import fields
class TestRelatedField(common.TransactionCase):
def setUp(self):
super(TestRelatedField, self).setUp()
self.partner = self.registry('res.partner')
self.company = self.registry('res.company')
def test_0_related(self):
""" test an usual related field """
# add a related field test_related_company_id on res.partner
old_columns = self.partner._columns
self.partner._columns = dict(old_columns)
self.partner._columns.update({
'related_company_partner_id': fields.related('company_id', 'partner_id', type='many2one', obj='res.partner'),
})
# find a company with a non-null partner_id
ids = self.company.search(self.cr, self.uid, [('partner_id', '!=', False)], limit=1)
id = ids[0]
# find partners that satisfy [('partner_id.company_id', '=', id)]
company_ids = self.company.search(self.cr, self.uid, [('partner_id', '=', id)])
partner_ids1 = self.partner.search(self.cr, self.uid, [('company_id', 'in', company_ids)])
partner_ids2 = self.partner.search(self.cr, self.uid, [('related_company_partner_id', '=', id)])
self.assertEqual(partner_ids1, partner_ids2)
# restore res.partner fields
self.partner._columns = old_columns
def do_test_company_field(self, field):
# get a partner with a non-null company_id
ids = self.partner.search(self.cr, self.uid, [('company_id', '!=', False)], limit=1)
partner = self.partner.browse(self.cr, self.uid, ids[0])
# check reading related field
self.assertEqual(partner[field], partner.company_id)
# check that search on related field is equivalent to original field
ids1 = self.partner.search(self.cr, self.uid, [('company_id', '=', partner.company_id.id)])
ids2 = self.partner.search(self.cr, self.uid, [(field, '=', partner.company_id.id)])
self.assertEqual(ids1, ids2)
def test_1_single_related(self):
""" test a related field with a single indirection like fields.related('foo') """
# add a related field test_related_company_id on res.partner
old_columns = self.partner._columns
self.partner._columns = dict(old_columns)
self.partner._columns.update({
'single_related_company_id': fields.related('company_id', type='many2one', obj='res.company'),
})
self.do_test_company_field('single_related_company_id')
# restore res.partner fields
self.partner._columns = old_columns
def test_2_related_related(self):
""" test a related field referring to a related field """
# add a related field on a related field on res.partner
old_columns = self.partner._columns
self.partner._columns = dict(old_columns)
self.partner._columns.update({
'single_related_company_id': fields.related('company_id', type='many2one', obj='res.company'),
'related_related_company_id': fields.related('single_related_company_id', type='many2one', obj='res.company'),
})
self.do_test_company_field('related_related_company_id')
# restore res.partner fields
self.partner._columns = old_columns
def test_3_read_write(self):
""" write on a related field """
# add a related field test_related_company_id on res.partner
old_columns = self.partner._columns
self.partner._columns = dict(old_columns)
self.partner._columns.update({
'related_company_partner_id': fields.related('company_id', 'partner_id', type='many2one', obj='res.partner'),
})
# find a company with a non-null partner_id
company_ids = self.company.search(self.cr, self.uid, [('partner_id', '!=', False)], limit=1)
company = self.company.browse(self.cr, self.uid, company_ids[0])
# find partners that satisfy [('partner_id.company_id', '=', company.id)]
partner_ids = self.partner.search(self.cr, self.uid, [('related_company_partner_id', '=', company.id)])
partner = self.partner.browse(self.cr, self.uid, partner_ids[0])
# create a new partner, and assign it to company
new_partner_id = self.partner.create(self.cr, self.uid, {'name': 'Foo'})
partner.write({'related_company_partner_id': new_partner_id})
company = self.company.browse(self.cr, self.uid, company_ids[0])
self.assertEqual(company.partner_id.id, new_partner_id)
partner = self.partner.browse(self.cr, self.uid, partner_ids[0])
self.assertEqual(partner.related_company_partner_id.id, new_partner_id)
# restore res.partner fields
self.partner._columns = old_columns
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -74,6 +74,19 @@ def py2exe_options():
execfile(join(os.path.dirname(__file__), 'openerp', 'release.py'))
# Notes for OpenERP developer on windows:
#
# To setup a windows developer evironement install python2.7 then pip and use
# "pip install <depencey>" for every dependency listed below.
#
# Dependecies that requires DLLs are not installable with pip install, for
# them we added comments with links where you can find the installers.
#
# OpenERP on windows also require the pywin32, the binary can be found at
# http://pywin32.sf.net
#
# Both python2.7 32bits and 64bits are known to work.
setuptools.setup(
name = 'openerp',
version = version,
@ -90,29 +103,30 @@ setuptools.setup(
dependency_links = ['http://download.gna.org/pychart/'],
#include_package_data = True,
install_requires = [
'pychart',
'pychart', # not on pypi, use: pip install http://download.gna.org/pychart/PyChart-1.39.tar.gz
'babel',
'docutils',
'feedparser',
'gdata',
'lxml < 3',
'lxml < 3', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
'mako',
'psutil',
'PIL', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
'psutil', # windows binary code.google.com/p/psutil/downloads/list
'psycopg2',
'pydot',
'python-dateutil < 2',
'python-ldap',
'python-ldap', # optional
'python-openid',
'pytz',
'pywebdav',
'pyyaml',
'reportlab',
'reportlab', # windows binary pypi.python.org/pypi/reportlab
'simplejson',
'vatnumber',
'vobject',
'werkzeug',
'xlwt',
'zsi',
'zsi', # optional
],
extras_require = {
'SSL' : ['pyopenssl'],