[MERGE] forward port of branch 9.0 up to a842199

This commit is contained in:
Denis Ledoux
2016-01-29 12:55:57 +01:00
16 changed files with 119 additions and 45 deletions
+1 -1
View File
@@ -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 """
@@ -262,16 +262,8 @@
<field name="arch" type="xml">
<xpath expr="//group[@name='account_grp']" position="after">
<group name="timesheets_grp" string="Timesheets">
<field name="timesheet_range"/>
<field name="timesheet_max_difference"/>
<div>
<label for="timesheet_range"/>
<field name="timesheet_range" required="1" class="oe_inline"/>
</div>
<div>
<label for="timesheet_max_difference"/>
<field name="timesheet_max_difference" required="1" class="oe_inline"/>
</div>
<field name="timesheet_range" required="1"/>
<field name="timesheet_max_difference" required="1"/>
</group>
</xpath>
</field>
+2
View File
@@ -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')
+1
View File
@@ -143,6 +143,7 @@ class MailController(http.Controller):
'id': res_id,
'active_id': res_id,
'view_id': record_sudo.get_formview_id(),
'action': record_action.get('id'),
}
url = '/web?%s#%s' % (url_encode(query), url_encode(url_params))
return werkzeug.utils.redirect(url)
@@ -10,13 +10,15 @@
<div class="row">
<div class="col-xs-6">
<p t-if="o.address_id == o.partner_invoice_id">Invoice and shipping address:</p>
<p t-if="o.address_id != o.partner_invoice_id">Invoice address: </p>
<div t-field="o.partner_invoice_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true, "phone_icons": true}'/>
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
<strong t-if="o.address_id == o.partner_invoice_id">Invoice and shipping address:</strong>
<div t-if="o.partner_invoice_id">
<strong t-if="o.address_id != o.partner_invoice_id">Invoice address: </strong>
<div t-field="o.partner_invoice_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true, "phone_icons": true}'/>
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div>
<div t-if="o.address_id != o.partner_invoice_id">
<p>Shipping address :</p>
<strong>Shipping address :</strong>
<div t-field="o.address_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true, "phone_icons": true}'/>
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+4 -2
View File
@@ -28,7 +28,8 @@ class sale_order(osv.Model):
)[document.partner_id.id]
def get_formview_action(self, cr, uid, id, context=None):
user = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context)
context = context or {}
user = self.pool['res.users'].browse(cr, SUPERUSER_ID, context.get('uid', uid), context=context)
if user.share:
document = self.browse(cr, uid, id, context=context)
action_xmlid = 'action_quotations_portal' if document.state in ('draft', 'sent') else 'action_orders_portal'
@@ -57,7 +58,8 @@ class account_invoice(osv.Model):
)[document.partner_id.id]
def get_formview_action(self, cr, uid, id, context=None):
user = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context)
context = context or {}
user = self.pool['res.users'].browse(cr, SUPERUSER_ID, context.get('uid', uid), context=context)
if user.share:
return self.pool['ir.actions.act_window'].for_xml_id(cr, uid, 'portal_sale', 'portal_action_invoices', context=context)
return super(account_invoice, self).get_formview_action(cr, uid, id, context=context)
+1 -1
View File
@@ -145,7 +145,7 @@
<button name="button_confirm" type="object" states="draft" string="Confirm Order" id="draft_confirm"/>
<button name="button_confirm" type="object" states="sent" string="Confirm Order" class="oe_highlight" id="bid_confirm"/>
<button name="button_approve" type="object" states='to approve' string="Approve Order" class="oe_highlight" groups="purchase.group_purchase_manager"/>
<button name="action_rfq_send" states="approved" string="Send PO by Email" type="object" context="{'send_rfq':False}"/>
<button name="action_rfq_send" states="purchase" string="Send PO by Email" type="object" context="{'send_rfq':False}"/>
<button name="action_view_picking" string="Receive Products" type="object" attrs="{'invisible': [('state','not in', ('purchase','done'))]}"/>
<button name="button_draft" states="cancel" string="Set to Draft" type="object" />
<button name="button_cancel" states="draft,to approve,sent,purchase" string="Cancel" type="object" />
+6
View File
@@ -51,6 +51,12 @@ msgstr ""
msgid " (reserved)"
msgstr ""
#. module: stock
#: code:addons/stock/stock.py:3182
#, python-format
msgid "You cannot have two inventory adjustements in state 'in Progess' with the same product(%s), same location(%s), same package, same owner and same lot. Please first validate the first inventory adjustement with this product before creating another one."
msgstr ""
#. module: stock
#: code:addons/stock/stock.py:3482
#, python-format
+8
View File
@@ -3172,6 +3172,14 @@ class stock_inventory_line(osv.osv):
if context is None:
context = {}
product_obj = self.pool.get('product.product')
dom = [('product_id', '=', values.get('product_id')), ('inventory_id.state', '=', 'confirm'),
('location_id', '=', values.get('location_id')), ('partner_id', '=', values.get('partner_id')),
('package_id', '=', values.get('package_id')), ('prod_lot_id', '=', values.get('prod_lot_id'))]
res = self.search(cr, uid, dom, context=context)
if res:
location = self.pool['stock.location'].browse(cr, uid, values.get('location_id'), context=context)
product = product_obj.browse(cr, uid, values.get('product_id'), context=context)
raise UserError(_("You cannot have two inventory adjustements in state 'in Progess' with the same product(%s), same location(%s), same package, same owner and same lot. Please first validate the first inventory adjustement with this product before creating another one.") % (product.name, location.name))
if 'product_id' in values and not 'product_uom_id' in values:
values['product_uom_id'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id
return super(stock_inventory_line, self).create(cr, uid, values, context=context)
@@ -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));
+1 -1
View File
@@ -478,7 +478,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
@@ -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']
+16 -5
View File
@@ -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
+1 -1
View File
@@ -91,7 +91,7 @@ class OdooHook(object):
""" Makes odoo package also available as openerp
"""
def find_module(self, name, path):
def find_module(self, name, path=None):
# openerp.addons.<identifier> should already be matched by AddonsHook,
# only framework and subdirectories of modules should match
if re.match(r'^odoo\b', name):
+6 -9
View File
@@ -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
+34
View File
@@ -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