Invoice and shipping address:
-Invoice address:
- -VAT:
-Shipping address :
+ Invoice and shipping address: +VAT:
+VAT:
diff --git a/addons/purchase/partner.py b/addons/purchase/partner.py index 6d29e79bd72..de9bf061c36 100644 --- a/addons/purchase/partner.py +++ b/addons/purchase/partner.py @@ -28,13 +28,17 @@ class res_partner(osv.osv): def _purchase_invoice_count(self, cr, uid, ids, field_name, arg, context=None): PurchaseOrder = self.pool['purchase.order'] Invoice = self.pool['account.invoice'] - return { - partner_id: { - 'purchase_order_count': PurchaseOrder.search_count(cr,uid, [('partner_id', 'child_of', partner_id)], context=context), - 'supplier_invoice_count': Invoice.search_count(cr,uid, [('partner_id', 'child_of', partner_id), ('type','=','in_invoice')], context=context) - } - for partner_id in ids - } + res = {} + + for partner_id in ids: + res[partner_id] = {} + + if 'purchase_order_count' in field_name: + res[partner_id]['purchase_order_count'] = PurchaseOrder.search_count(cr,uid, [('partner_id', 'child_of', partner_id)], context=context) + if 'supplier_invoice_count' in field_name: + res[partner_id]['supplier_invoice_count'] = Invoice.search_count(cr,uid, [('partner_id', 'child_of', partner_id), ('type','=','in_invoice')], context=context) + + return res def _commercial_fields(self, cr, uid, context=None): return super(res_partner, self)._commercial_fields(cr, uid, context=context) + ['property_product_pricelist_purchase'] diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js index b36a927307a..86702b52f73 100644 --- a/addons/web/static/src/js/data_export.js +++ b/addons/web/static/src/js/data_export.js @@ -384,6 +384,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ }, add_field: function(field_id, string) { var field_list = this.$el.find('#fields_list'); + field_id = this.records[field_id] || field_id; if (this.$el.find("#fields_list option[value='" + field_id + "']") && !this.$el.find("#fields_list option[value='" + field_id + "']").length) { field_list.append(new Option(string, field_id)); diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 5ba024a848b..59c050bc225 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -471,7 +471,7 @@ class website(osv.osv): :rtype: bool """ endpoint = rule.endpoint - methods = endpoint.routing.get('method') or ['GET'] + methods = endpoint.routing.get('methods') or ['GET'] converters = rule._converters.values() if not ('GET' in methods diff --git a/openerp/addons/test_new_api/tests/test_new_fields.py b/openerp/addons/test_new_api/tests/test_new_fields.py index 92d1aedc8db..8325ba0a0bd 100644 --- a/openerp/addons/test_new_api/tests/test_new_fields.py +++ b/openerp/addons/test_new_api/tests/test_new_fields.py @@ -392,28 +392,46 @@ class TestNewFields(common.TransactionCase): @mute_logger('openerp.addons.base.ir.ir_model') def test_41_new_related(self): - """ test the behavior of related fields on new records. """ - discussion = self.env.ref('test_new_api.discussion_0') - access = self.env.ref('test_new_api.access_discussion') - + """ test the behavior of related fields starting on new records. """ # make discussions unreadable for demo user + access = self.env.ref('test_new_api.access_discussion') access.write({'perm_read': False}) # create an environment for demo user - demo_env = self.env(user=self.env.ref('base.user_demo')) - self.assertEqual(demo_env.user.login, "demo") + env = self.env(user=self.env.ref('base.user_demo')) + self.assertEqual(env.user.login, "demo") # create a new message as demo user - values = {'discussion': discussion.id} - message = demo_env['test_new_api.message'].new(values) + discussion = self.env.ref('test_new_api.discussion_0') + message = env['test_new_api.message'].new({'discussion': discussion}) self.assertEqual(message.discussion, discussion) # read the related field discussion_name - self.assertEqual(message.discussion.env, demo_env) + self.assertEqual(message.discussion.env, env) self.assertEqual(message.discussion_name, discussion.name) with self.assertRaises(AccessError): message.discussion.name + @mute_logger('openerp.addons.base.ir.ir_model') + def test_42_new_related(self): + """ test the behavior of related fields traversing new records. """ + # make discussions unreadable for demo user + access = self.env.ref('test_new_api.access_discussion') + access.write({'perm_read': False}) + + # create an environment for demo user + env = self.env(user=self.env.ref('base.user_demo')) + self.assertEqual(env.user.login, "demo") + + # create a new discussion and a new message as demo user + discussion = env['test_new_api.discussion'].new({'name': 'Stuff'}) + message = env['test_new_api.message'].new({'discussion': discussion}) + self.assertEqual(message.discussion, discussion) + + # read the related field discussion_name + self.assertNotEqual(message.sudo().env, message.env) + self.assertEqual(message.discussion_name, discussion.name) + def test_50_defaults(self): """ test default values. """ fields = ['discussion', 'body', 'author', 'size'] diff --git a/openerp/fields.py b/openerp/fields.py index 3cb23534954..7f2e2d3d764 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -30,6 +30,7 @@ import logging import pytz import xmlrpclib +from openerp.sql_db import LazyCursor from openerp.tools import float_round, frozendict, html_sanitize, ustr, OrderedSet from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT @@ -58,6 +59,15 @@ def _check_value(value): """ Return ``value``, or call its getter if ``value`` is a :class:`SpecialValue`. """ return value.get() if isinstance(value, SpecialValue) else value +def copy_cache(records, env): + """ Recursively copy the cache of ``records`` to the environment ``env``. """ + for record, target in zip(records, records.with_env(env)): + if not target._cache: + for name, value in record._cache.iteritems(): + target[name] = value + if isinstance(value, BaseModel): + copy_cache(value, env) + def resolve_all_mro(cls, name, reverse=False): """ Return the (successively overridden) values of attribute ``name`` in ``cls`` @@ -538,10 +548,9 @@ class Field(object): # when related_sudo, bypass access rights checks when reading values others = records.sudo() if self.related_sudo else records for record, other in zip(records, others): - if not record.id: + if not record.id and record.env != other.env: # draft records: copy record's cache to other's cache first - for name, value in record._cache.iteritems(): - other[name] = value + copy_cache(record, other.env) # traverse the intermediate fields; follow the first record at each step for name in self.related[:-1]: other = other[name][:1] @@ -1062,7 +1071,7 @@ class Float(Field): @property def digits(self): if callable(self._digits): - with fields._get_cursor() as cr: + with LazyCursor() as cr: return self._digits(cr) else: return self._digits @@ -1087,6 +1096,8 @@ class Float(Field): def convert_to_cache(self, value, record, validate=True): # apply rounding here, otherwise value in cache may be wrong! value = float(value or 0.0) + if not validate: + return value digits = self.digits return float_round(value, precision_digits=digits[1]) if digits else value @@ -1874,7 +1885,7 @@ class Id(Field): raise TypeError("field 'id' cannot be assigned") # imported here to avoid dependency cycle issues -from openerp import SUPERUSER_ID, registry +from openerp import SUPERUSER_ID from .exceptions import Warning, AccessError, MissingError from .models import BaseModel, MAGIC_COLUMNS from .osv import fields diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 5df069506c5..2f5d909de96 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -65,7 +65,7 @@ class AddonsImportHook(object): thus `import openerp.addons.module`. """ - def find_module(self, module_name, package_path): + def find_module(self, module_name, package_path=None): module_parts = module_name.split('.') if len(module_parts) == 3 and module_name.startswith('openerp.addons.'): return self # We act as a loader too. diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 902daa8f865..53a2a6aace0 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -43,21 +43,18 @@ import pytz import re import xmlrpclib from operator import itemgetter -from contextlib import contextmanager from psycopg2 import Binary import openerp import openerp.tools as tools +from openerp.sql_db import LazyCursor from openerp.tools.translate import _ from openerp.tools import float_repr, float_round, frozendict, html_sanitize import simplejson -from openerp import SUPERUSER_ID, registry +from openerp import SUPERUSER_ID -@contextmanager -def _get_cursor(): - # yield a valid cursor from any environment or create a new one if none found - with registry().cursor() as cr: - yield cr +# deprecated; kept for backward compatibility only +_get_cursor = LazyCursor EMPTY_DICT = frozendict() @@ -396,7 +393,7 @@ class float(_column): @property def digits(self): if self._digits_compute: - with _get_cursor() as cr: + with LazyCursor() as cr: return self._digits_compute(cr) else: return self._digits @@ -1323,7 +1320,7 @@ class function(_column): @property def digits(self): if self._digits_compute: - with _get_cursor() as cr: + with LazyCursor() as cr: return self._digits_compute(cr) else: return self._digits diff --git a/openerp/sql_db.py b/openerp/sql_db.py index 076b20c578b..bee4161c47e 100644 --- a/openerp/sql_db.py +++ b/openerp/sql_db.py @@ -443,6 +443,40 @@ class TestCursor(Cursor): self.execute("ROLLBACK TO SAVEPOINT test_cursor") self.execute("SAVEPOINT test_cursor") +class LazyCursor(object): + """ A proxy object to a cursor. The cursor itself is allocated only if it is + needed. This class is useful for cached methods, that use the cursor + only in the case of a cache miss. + """ + def __init__(self, dbname=None): + self._dbname = dbname + self._cursor = None + self._depth = 0 + + @property + def dbname(self): + return self._dbname or threading.currentThread().dbname + + def __getattr__(self, name): + cr = self._cursor + if cr is None: + from openerp import registry + cr = self._cursor = registry(self.dbname).cursor() + for _ in xrange(self._depth): + cr.__enter__() + return getattr(cr, name) + + def __enter__(self): + self._depth += 1 + if self._cursor is not None: + self._cursor.__enter__() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._depth -= 1 + if self._cursor is not None: + self._cursor.__exit__(exc_type, exc_value, traceback) + class PsycoConnection(psycopg2.extensions.connection): pass