diff --git a/addons/account/tests/test_tax.py b/addons/account/tests/test_tax.py index 2b9e11045e9..d221e705412 100644 --- a/addons/account/tests/test_tax.py +++ b/addons/account/tests/test_tax.py @@ -91,7 +91,7 @@ class TestTax(AccountTestUsers): def test_tax_currency(self): self.division_tax.amount = 15.0 res = self.division_tax.compute_all(200.0, currency=self.env.ref('base.VEF')) - self.assertEquals(res['total_included'], 235.2941) + self.assertAlmostEqual(res['total_included'], 235.2941) def test_tax_move_lines_creation(self): """ Test that creating a move.line with tax_ids generates the tax move lines and adjust line amount when a tax is price_include """ diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index 7fc5aa8798b..84e77065736 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -195,6 +195,7 @@ class EscposDriver(Thread): localips = ['0.0.0.0','127.0.0.1','127.0.1.1'] hosting_ap = os.system('pgrep hostapd') == 0 ssid = subprocess.check_output('iwconfig 2>&1 | grep \'ESSID:"\' | sed \'s/.*"\\(.*\\)"/\\1/\'', shell=True).rstrip() + mac = subprocess.check_output('ifconfig | grep -B 1 \'inet addr\' | grep -o \'HWaddr .*\' | sed \'s/HWaddr //\'', shell=True).rstrip() ips = [ c.split(':')[1].split(' ')[0] for c in commands.getoutput("/sbin/ifconfig").split('\n') if 'inet addr' in c ] ips = [ ip for ip in ips if ip not in localips ] eprint.text('\n\n') @@ -218,6 +219,7 @@ class EscposDriver(Thread): eprint.text(ip+'\n') if len(ips) >= 1: + eprint.text('\nMAC Address:\n' + mac + '\n') eprint.text('\nHomepage:\nhttp://'+ips[0]+':8069\n') eprint.text('\n\n') diff --git a/addons/mrp_repair/views/report_mrprepairorder.xml b/addons/mrp_repair/views/report_mrprepairorder.xml index c9ca68c39b3..0e66cba9ebc 100644 --- a/addons/mrp_repair/views/report_mrprepairorder.xml +++ b/addons/mrp_repair/views/report_mrprepairorder.xml @@ -10,13 +10,15 @@
Invoice and shipping address:
-Invoice address:
- -VAT:
+ Invoice and shipping address: +VAT:
+Shipping address :
+ Shipping address :VAT:
diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js index 57159732ad6..a73020ce9b2 100644 --- a/addons/web/static/src/js/widgets/data_export.js +++ b/addons/web/static/src/js/widgets/data_export.js @@ -388,6 +388,7 @@ var DataExport = 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 4aa3deb1d4c..9e3249f1400 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -468,7 +468,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 ba8cb0516c3..8a7d9225f3b 100644 --- a/openerp/addons/test_new_api/tests/test_new_fields.py +++ b/openerp/addons/test_new_api/tests/test_new_fields.py @@ -422,28 +422,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 7576a2a4f3c..e86e3582f59 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -12,6 +12,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 @@ -41,6 +42,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_mro(model, name, predicate): """ Return the list of successively overridden values of attribute ``name`` @@ -560,10 +570,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) other, field = self.traverse_related(other) record[self.name] = other[field.name] @@ -1074,7 +1083,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 @@ -1091,6 +1100,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 @@ -1980,7 +1991,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 check_pg_name, BaseModel, MAGIC_COLUMNS from .osv import fields diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 18687e865c0..e50ba86fc39 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -46,7 +46,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 294bf6a817b..9411c1e4df6 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -25,21 +25,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 json -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() @@ -378,7 +375,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 @@ -1354,7 +1351,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 749af2376c0..4aef9549ff4 100644 --- a/openerp/sql_db.py +++ b/openerp/sql_db.py @@ -456,6 +456,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