[MERGE] forward port of branch 8.0 up to edeb5a8

This commit is contained in:
Denis Ledoux
2016-01-29 11:41:24 +01:00
15 changed files with 135 additions and 57 deletions
+4 -4
View File
@@ -1585,7 +1585,7 @@ class account_move(osv.osv):
obj_move_line.write(cr, uid, line_draft_ids, {
'state': 'valid'
}, context, check=False)
}, context=context, check=False)
account = {}
account2 = {}
@@ -1604,7 +1604,7 @@ class account_move(osv.osv):
obj_move_line.write(cr, uid, [line.id], {
'tax_code_id': code,
'tax_amount': amount
}, context, check=False)
}, context=context, check=False)
elif journal.centralisation:
# If the move is not balanced, it must be centralised...
@@ -1619,7 +1619,7 @@ class account_move(osv.osv):
self._centralise(cr, uid, move, 'credit', context=context)
obj_move_line.write(cr, uid, line_draft_ids, {
'state': 'valid'
}, context, check=False)
}, context=context, check=False)
else:
# We can't validate it (it's unbalanced)
# Setting the lines as draft
@@ -1627,7 +1627,7 @@ class account_move(osv.osv):
if not_draft_line_ids:
obj_move_line.write(cr, uid, not_draft_line_ids, {
'state': 'draft'
}, context, check=False)
}, context=context, check=False)
# Create analytic lines for the valid moves
for record in valid_moves:
obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
@@ -3,7 +3,7 @@ from datetime import datetime
from mock import patch
from openerp.tests.common import TransactionCase
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare
# ---------------------------------------------------------
@@ -32,65 +32,71 @@ class TestTheoreticalAmount(TransactionCase):
self.patcher = patch('openerp.addons.account_budget.account_budget.datetime', wraps=datetime)
self.mock_datetime = self.patcher.start()
def assertFloatEqual(self, value1, value2, *args, **kwargs):
""" Compare two values of the field theoritical_amount """
digits = type(self.line).theoritical_amount.digits
result = float_compare(value1, value2, precision_digits=digits[1])
return self.assertFalse(result, *args, **kwargs)
def test_01(self):
"""Start"""
date = datetime.strptime('2014-01-01 00:00:00', DEFAULT_SERVER_DATETIME_FORMAT)
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, 0)
self.assertFloatEqual(self.line.theoritical_amount, 0)
def test_02(self):
"""After 24 hours"""
date = datetime.strptime('2014-01-02 00:00:00', DEFAULT_SERVER_DATETIME_FORMAT)
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, -1)
self.assertFloatEqual(self.line.theoritical_amount, -1)
def test_03(self):
"""After 36 hours"""
date = datetime.strptime('2014-01-02 12:00:00', DEFAULT_SERVER_DATETIME_FORMAT)
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, -1.5)
self.assertFloatEqual(self.line.theoritical_amount, -1.5)
def test_04(self):
"""After 48 hours"""
date = datetime.strptime('2014-01-03 00:00:00', DEFAULT_SERVER_DATETIME_FORMAT)
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, -2)
self.assertFloatEqual(self.line.theoritical_amount, -2)
def test_05(self):
"""After 10 days"""
date = datetime.strptime('2014-01-11 00:00:00', DEFAULT_SERVER_DATETIME_FORMAT)
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, -10)
self.assertFloatEqual(self.line.theoritical_amount, -10)
def test_06(self):
"""After 50 days"""
date = datetime.strptime('2014-02-20 00:00:00', DEFAULT_SERVER_DATETIME_FORMAT)
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, -50)
self.assertFloatEqual(self.line.theoritical_amount, -50)
def test_07(self):
"""After 182 days, exactly half of the budget line"""
date = datetime.strptime('2014-07-02 00:00:00', DEFAULT_SERVER_DATETIME_FORMAT)
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, -182)
self.assertFloatEqual(self.line.theoritical_amount, -182)
def test_08(self):
"""After 308 days at noon"""
date = datetime.strptime('2014-11-05 12:00:00', DEFAULT_SERVER_DATETIME_FORMAT) # remember, remember
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, -308.5)
self.assertFloatEqual(self.line.theoritical_amount, -308.5)
def test_09(self):
"""One day before"""
date = datetime.strptime('2014-12-30 00:00:00', DEFAULT_SERVER_DATETIME_FORMAT)
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, -363)
self.assertFloatEqual(self.line.theoritical_amount, -363)
def test_10(self):
"""At last"""
date = datetime.strptime('2014-12-31 00:00:00', DEFAULT_SERVER_DATETIME_FORMAT)
self.mock_datetime.now.return_value = date
self.assertEqual(self.line.theoritical_amount, -364)
self.assertFloatEqual(self.line.theoritical_amount, -364)
def tearDown(self):
self.patcher.stop()
@@ -1494,6 +1494,7 @@ class account_voucher_line(osv.osv):
'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
'state': fields.related('voucher_id', 'state', type='char', string='State', readonly=True),
}
_defaults = {
'name': '',
@@ -163,10 +163,11 @@
<label for="line_dr_ids"/>
<field name="line_dr_ids" context="{'journal_id':journal_id, 'type':type, 'partner_id':partner_id}" on_change="onchange_line_ids(line_dr_ids, line_cr_ids, amount, currency_id, type, context)">
<tree string="Supplier Invoices and Outstanding transactions" editable="bottom" colors="gray:amount==0">
<field name="state" invisible="1"/>
<field name="move_line_id" context="{'journal_id':parent.journal_id, 'partner_id':parent.partner_id}"
on_change="onchange_move_line_id(move_line_id)"
domain="[('account_id.type','=','payable'), ('reconcile_id','=', False), ('partner_id','=',parent.partner_id)]"
required="1"/>
attrs="{'required': [('state', 'in', [False, 'draft'])]}"/>
<field name="account_id" groups="base.group_no_one" domain="[('type','=','payable')]"/>
<field name="date_original" readonly="1"/>
<field name="date_due" readonly="1"/>
@@ -179,10 +180,11 @@
<label for="line_cr_ids" attrs="{'invisible': [('pre_line','=',False)]}"/>
<field name="line_cr_ids" attrs="{'invisible': [('pre_line','=',False)]}" context="{'journal_id':journal_id, 'partner_id':partner_id}" on_change="onchange_line_ids(line_dr_ids, line_cr_ids, amount, currency_id, type, context)">
<tree string="Credits" editable="bottom" colors="gray:amount==0">
<field name="state" invisible="1"/>
<field name="move_line_id" context="{'journal_id':parent.journal_id, 'partner_id':parent.partner_id}"
on_change="onchange_move_line_id(move_line_id)"
domain="[('account_id.type','=','payable'), ('reconcile_id','=', False), ('partner_id','=',parent.partner_id)]"
required="1"/>
attrs="{'required': [('state', 'in', [False, 'draft'])]}"/>
<field name="account_id" groups="base.group_no_one" domain="[('type','=','payable')]"/>
<field name="date_original" readonly="1"/>
<field name="date_due" readonly="1"/>
+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 -1
View File
@@ -46,7 +46,7 @@ class LivechatController(http.Controller):
'regex_url' : rule.regex_url,
}
info['rule'] = json.dumps(rule and rule_data or False)
return request.render('im_livechat.loader', info)
return request.render('im_livechat.loader', info, headers=[('Content-Type', 'application/javascript')])
@http.route('/im_livechat/get_session', type="json", auth="none")
def get_session(self, channel_id, anonymous_name, **kwargs):
@@ -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}'/>
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
<div t-if="o.address_id != o.partner_invoice_id">
<p>Shipping address :</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}'/>
<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" class="mt8">
<strong>Shipping address :</strong>
<div t-field="o.address_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+11 -7
View File
@@ -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']
+1
View File
@@ -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));
+1 -1
View File
@@ -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
@@ -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']
+16 -5
View File
@@ -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
+1 -1
View File
@@ -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.
+6 -9
View File
@@ -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
+34
View File
@@ -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