[MERGE] forward port of branch 9.0 up to a842199
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user