diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py
index 01bb904c3f7..2353ec6fd7a 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -792,12 +792,19 @@ class TestReconciliation(AccountingTestCase):
statement = self.make_payment(invoice, journal, 50)
+ # The report searches on the create_date to dispatch reconciled lines to report periods
+ # Also, in this case, there can be only 1 partial_reconcile
+ statement_partial_id = statement.move_line_ids.mapped(lambda l: l.matched_credit_ids + l.matched_debit_ids)
+ self.env.cr.execute('UPDATE account_partial_reconcile SET create_date = %(date)s WHERE id = %(partial_id)s',
+ {'date': report_date_to + ' 00:00:00',
+ 'partial_id': statement_partial_id.id})
+
# Case 1: The invoice and payment are reconciled: Nothing should appear
report_lines, total, amls = AgedReport._get_partner_move_lines(account_type, report_date_to, 'posted', 30)
partner_lines = [line for line in report_lines if line['partner_id'] == partner.id]
self.assertEqual(partner_lines, [], 'The aged receivable shouldn\'t have lines at this point')
- self.assertFalse(partner.id in amls, 'The aged receivable should not have amls either')
+ self.assertFalse(amls.get(partner.id, False), 'The aged receivable should not have amls either')
# Case 2: The invoice and payment are not reconciled: we should have one line on the report
# and 2 amls
diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js
index ca9ace9422b..f0cb213f022 100644
--- a/addons/board/static/src/js/dashboard.js
+++ b/addons/board/static/src/js/dashboard.js
@@ -116,10 +116,9 @@ FormController.include({
this.do_action({
type: 'ir.actions.act_window',
res_model: event.data.model,
- views: [[false, 'form']],
+ views: [[event.data.formViewID || false, 'form']],
res_id: event.data.res_id,
});
-
},
});
@@ -133,6 +132,9 @@ FormRenderer.include({
'click .oe_dashboard_link_change_layout': '_onChangeLayout',
'click .oe_dashboard_column .oe_close': '_onCloseAction',
}),
+ custom_events: _.extend({}, FormRenderer.prototype.custom_events, {
+ switch_view: '_onSwitchView',
+ }),
/**
* @override
@@ -142,6 +144,7 @@ FormRenderer.include({
this.noContentHelp = params.noContentHelp;
this.actionsDescr = {};
this._boardSubcontrollers = []; // for board: controllers of subviews
+ this._boardFormViewIDs = {}; // for board: mapping subview controller to form view id
},
/**
* Call `on_attach_callback` for each subview
@@ -268,6 +271,11 @@ FormRenderer.include({
hasSelectors: false,
});
return view.getController(self).then(function (controller) {
+ self._boardFormViewIDs[controller.handle] = _.first(
+ _.find(action.views, function (descr) {
+ return descr[1] === 'form';
+ })
+ );
self._boardSubcontrollers.push(controller);
return controller.appendTo(params.$node);
});
@@ -396,6 +404,16 @@ FormRenderer.include({
$action.find('.oe_content').toggle();
this.trigger_up('save_dashboard');
},
+ /**
+ * Let FormController know which form view it should display based on the
+ * window action of the sub controller that is switching view
+ *
+ * @private
+ * @param {OdooEvent} event
+ */
+ _onSwitchView: function (event) {
+ event.data.formViewID = this._boardFormViewIDs[event.target.handle];
+ },
});
});
diff --git a/addons/board/static/tests/dashboard_tests.js b/addons/board/static/tests/dashboard_tests.js
index 09af3e8e2cb..2680e7ea198 100644
--- a/addons/board/static/tests/dashboard_tests.js
+++ b/addons/board/static/tests/dashboard_tests.js
@@ -272,6 +272,51 @@ QUnit.test('can open a record', function (assert) {
form.destroy();
});
+QUnit.test('can open record using action form view', function (assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'board',
+ data: this.data,
+ arch: '
',
+ mockRPC: function (route) {
+ if (route === '/web/action/load') {
+ return $.when({
+ res_model: 'partner',
+ views: [[4, 'list'], [5, 'form']],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ 'partner,4,list':
+ '',
+ 'partner,5,form':
+ '',
+ },
+ intercepts: {
+ do_action: function (event) {
+ assert.deepEqual(event.data.action, {
+ res_id: 1,
+ res_model: 'partner',
+ type: 'ir.actions.act_window',
+ views: [[5, 'form']],
+ }, "should do a do_action with correct parameters");
+ },
+ },
+ });
+
+ form.$('tr.o_data_row td:contains(yop)').click();
+ form.destroy();
+});
+
QUnit.test('can drag and drop a view', function (assert) {
assert.expect(4);
diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py
index fa57b18501e..805d8388a8a 100644
--- a/addons/hw_escpos/controllers/main.py
+++ b/addons/hw_escpos/controllers/main.py
@@ -8,6 +8,7 @@ import os
import os.path
import subprocess
import time
+import netifaces as ni
import traceback
try:
@@ -29,9 +30,10 @@ except ImportError:
usb = None
from odoo import http, _
-
from odoo.addons.hw_proxy.controllers import main as hw_proxy
+from uuid import getnode as get_mac
+
_logger = logging.getLogger(__name__)
# workaround https://bugs.launchpad.net/openobject-server/+bug/947231
@@ -196,10 +198,18 @@ class EscposDriver(Thread):
def print_status(self,eprint):
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 subprocess.check_output("/sbin/ifconfig").split('\n') if 'inet addr' in c ]
- ips = [ ip for ip in ips if ip not in localips ]
+ ssid = subprocess.check_output('iwconfig 2>&1 | grep \'ESSID:"\' | sed \'s/.*"\\(.*\\)"/\\1/\'', shell=True).decode('utf-8').rstrip()
+ mac = get_mac()
+ h = iter(hex(mac)[2:].zfill(12))
+ mac = ":".join(i + next(h) for i in h)
+ interfaces = ni.interfaces()
+ ips = []
+ for iface_id in interfaces:
+ iface_obj = ni.ifaddresses(iface_id)
+ ifconfigs = iface_obj.get(ni.AF_INET, [])
+ for conf in ifconfigs:
+ if conf.get('addr'):
+ ips.append(conf.get('addr'))
eprint.text('\n\n')
eprint.set(align='center',type='b',height=2,width=2)
eprint.text('PosBox Status\n')
@@ -221,8 +231,10 @@ 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')
+ ips_filtered = [i for i in ips if i != '127.0.0.1']
+ main_ips = ips_filtered and ips_filtered[0] or '127.0.0.1'
+ eprint.text('\nMAC Address:\n' + str(mac) + '\n')
+ eprint.text('\nHomepage:\nhttp://' + main_ips + ':8069\n')
eprint.text('\n\n')
eprint.cut()
diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py
index f31b1619894..7fc0e196b39 100644
--- a/addons/hw_escpos/escpos/escpos.py
+++ b/addons/hw_escpos/escpos/escpos.py
@@ -7,6 +7,7 @@ import io
import math
import re
import traceback
+import codecs
from hashlib import md5
from PIL import Image
@@ -318,9 +319,9 @@ class Escpos:
else:
image_border = 32 - (size % 32)
if (image_border % 2) == 0:
- return (image_border / 2, image_border / 2)
+ return (int(image_border / 2), int(image_border / 2))
else:
- return (image_border / 2, (image_border / 2) + 1)
+ return (int(image_border / 2), int((image_border / 2) + 1))
def _print_image(self, line, size):
""" Print formatted image """
@@ -330,8 +331,8 @@ class Escpos:
self._raw(S_RASTER_N)
- buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1], 0)
- self._raw(buffer.decode('hex'))
+ buffer = b"%02X%02X%02X%02X" % (int((size[0]/size[1])/8), 0, size[1], 0)
+ self._raw(codecs.decode(buffer, 'hex'))
buffer = ""
while i < len(line):
@@ -340,7 +341,7 @@ class Escpos:
i += 8
cont += 1
if cont % 4 == 0:
- self._raw(buffer.decode("hex"))
+ self._raw(codecs.decode(buffer, "hex"))
buffer = ""
cont = 0
@@ -349,7 +350,7 @@ class Escpos:
i = 0
cont = 0
buffer = ""
- raw = ""
+ raw = b""
def __raw(string):
if output:
@@ -358,8 +359,8 @@ class Escpos:
self._raw(string)
raw += S_RASTER_N
- buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1], 0)
- raw += buffer.decode('hex')
+ buffer = "%02X%02X%02X%02X" % (int((size[0]/size[1])/8), 0, size[1], 0)
+ raw += codecs.decode(buffer, 'hex')
buffer = ""
while i < len(line):
@@ -368,7 +369,7 @@ class Escpos:
i += 8
cont += 1
if cont % 4 == 0:
- raw += buffer.decode("hex")
+ raw += codecs.decode(buffer, 'hex')
buffer = ""
cont = 0
@@ -433,14 +434,14 @@ class Escpos:
print('print_b64_img')
- id = md5(img.encode('utf-8')).digest()
+ id = md5(img).digest()
if id not in self.img_cache:
print('not in cache')
- img = img[img.find(',')+1:]
- f = io.BytesIO('img')
- f.write(base64.decodestring(img))
+ img = img[img.find(b',')+1:]
+ f = io.BytesIO(b'img')
+ f.write(base64.decodebytes(img))
f.seek(0)
img_rgba = Image.open(f)
img = Image.new('RGB', img_rgba.size, (255,255,255))
@@ -527,7 +528,7 @@ class Escpos:
# https://github.com/python-escpos/python-escpos/pull/98/files#diff-a0b1df12c7c67e38915adbe469051e2dR444
self._raw('\x00')
else:
- raise exception.BarcodeCodeError()
+ raise BarcodeCodeError()
def receipt(self,xml):
"""
@@ -677,7 +678,7 @@ class Escpos:
elif elem.tag == 'img':
if 'src' in elem.attrib and 'data:' in elem.attrib['src']:
- self.print_base64_image(elem.attrib['src'])
+ self.print_base64_image(bytes(elem.attrib['src'], 'utf-8'))
elif elem.tag == 'barcode' and 'encoding' in elem.attrib:
serializer.start_block(stylestack)
@@ -797,19 +798,19 @@ class Escpos:
(encoding, _) = remaining.popitem()
else:
encoding = 'cp437'
- encoded = '\xb1' # could not encode, output error character
+ encoded = b'\xb1' # could not encode, output error character
break;
if encoding != self.encoding:
# if the encoding changed, remember it and prefix the character with
# the esc-pos encoding change sequence
self.encoding = encoding
- encoded = encodings[encoding] + encoded
+ encoded = bytes(encodings[encoding], 'utf-8') + encoded
return encoded
def encode_str(txt):
- buffer = ''
+ buffer = b''
for c in txt:
buffer += encode_char(c)
return buffer
diff --git a/addons/hw_posbox_homepage/controllers/main.py b/addons/hw_posbox_homepage/controllers/main.py
index 73ce2a70a13..6ba6c1ff62a 100644
--- a/addons/hw_posbox_homepage/controllers/main.py
+++ b/addons/hw_posbox_homepage/controllers/main.py
@@ -58,7 +58,7 @@ index_template = """
%s
- The PosBox software installed on this posbox is version 16,
+ The PosBox software installed on this posbox is version 17 BETA,
the posbox version number is independent from Odoo. You can upgrade
the software on the upgrade page.
diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py
index 3d42918c2ad..31d718616e4 100644
--- a/addons/hw_proxy/controllers/main.py
+++ b/addons/hw_proxy/controllers/main.py
@@ -21,6 +21,8 @@ BANNED_DEVICES = {
"0424:9514", # Standard Microsystem Corp. Builtin Ethernet module
"1d6b:0002", # Linux Foundation 2.0 root hub
"0424:ec00", # Standard Microsystem Corp. Other Builtin Ethernet module
+ "0424:2514", # Standard Microsystems Corp. USB 2.0 Hub (rpi3b+)
+ "0424:7800", # Standard Microsystems Corp. (rpi3b+)
}
@@ -100,15 +102,16 @@ class Proxy(http.Controller):
"""
if debug is None:
resp += """(debug version)"""
- devices = subprocess.check_output("lsusb").split('\n')
+ devices = subprocess.check_output("lsusb").decode('utf-8').split('\n')
count = 0
resp += "
\n"
for device in devices:
device_name = device[device.find('ID')+2:]
- device_id = device_name.split()[0]
- if not (device_id in BANNED_DEVICES):
- resp += "
"+device_name+"
\n"
- count += 1
+ if device_name: # to avoid last empty line
+ device_id = device_name.split()[0]
+ if not (device_id in BANNED_DEVICES):
+ resp += "
"+device_name+"
\n"
+ count += 1
if count == 0:
resp += "
No USB Device Found
"
@@ -124,7 +127,7 @@ class Proxy(http.Controller):
%s
- """ % subprocess.check_output('lsusb -v', shell=True)
+ """ % subprocess.check_output('lsusb -v', shell=True).decode('utf-8')
return request.make_response(resp,{
'Cache-Control': 'no-cache',
diff --git a/addons/hw_scale/controllers/main.py b/addons/hw_scale/controllers/main.py
index 090e6193fe6..162ab7a866d 100644
--- a/addons/hw_scale/controllers/main.py
+++ b/addons/hw_scale/controllers/main.py
@@ -28,7 +28,7 @@ except ImportError:
def _toledo8217StatusParse(status):
""" Parse a scale's status, returning a `(weight, weight_info)` pair. """
weight, weight_info = None, None
- stat = ord(status[status.index('?') + 1])
+ stat = status[status.index(b'?') + 1]
if stat == 0:
weight_info = 'ok'
else:
@@ -68,17 +68,17 @@ Toledo8217Protocol = ScaleProtocol(
parity=serial.PARITY_EVEN,
timeout=1,
writeTimeout=1,
- weightRegexp="\x02\\s*([0-9.]+)N?\\r",
- statusRegexp="\x02\\s*(\\?.)\\r",
+ weightRegexp=b"\x02\\s*([0-9.]+)N?\\r",
+ statusRegexp=b"\x02\\s*(\\?.)\\r",
statusParse=_toledo8217StatusParse,
commandDelay=0.2,
weightDelay=0.5,
newWeightDelay=0.2,
- commandTerminator='',
- weightCommand='W',
- zeroCommand='Z',
- tareCommand='T',
- clearCommand='C',
+ commandTerminator=b'',
+ weightCommand=b'W',
+ zeroCommand=b'Z',
+ tareCommand=b'T',
+ clearCommand=b'C',
emptyAnswerValid=False,
autoResetWeight=False,
)
@@ -98,15 +98,15 @@ ADAMEquipmentProtocol = ScaleProtocol(
weightRegexp=r"\s*([0-9.]+)kg", # LABEL format 3 + KG in the scale settings, but Label 1/2 should work
statusRegexp=None,
statusParse=None,
- commandTerminator="\r\n",
+ commandTerminator=b"\r\n",
commandDelay=0.2,
weightDelay=0.5,
newWeightDelay=5, # AZExtra beeps every time you ask for a weight that was previously returned!
# Adding an extra delay gives the operator a chance to remove the products
# before the scale starts beeping. Could not find a way to disable the beeps.
- weightCommand='P',
- zeroCommand='Z',
- tareCommand='T',
+ weightCommand=b'P',
+ zeroCommand=b'Z',
+ tareCommand=b'T',
clearCommand=None, # No clear command -> Tare again
emptyAnswerValid=True, # AZExtra does not answer unless a new non-zero weight has been detected
autoResetWeight=True, # AZExtra will not return 0 after removing products
@@ -165,8 +165,8 @@ class Scale(Thread):
if not char:
break
else:
- answer.append(char)
- return ''.join(answer)
+ answer.append(bytes(char))
+ return b''.join(answer)
def _parse_weight_answer(self, protocol, answer):
""" Parse a scale's answer to a weighing request, returning
diff --git a/addons/l10n_fr/data/account_chart_template_data.xml b/addons/l10n_fr/data/account_chart_template_data.xml
index aac0137d150..d184a621db5 100644
--- a/addons/l10n_fr/data/account_chart_template_data.xml
+++ b/addons/l10n_fr/data/account_chart_template_data.xml
@@ -1251,6 +1251,7 @@
Prêts participatifs2741
+
@@ -1258,6 +1259,7 @@
Prêts aux associés2742
+
@@ -1265,6 +1267,7 @@
Prêts au personnel2743
+
@@ -1272,6 +1275,7 @@
Autres prêts2748
+
@@ -1279,6 +1283,7 @@
Dépôts2751
+
@@ -1286,6 +1291,7 @@
Cautionnements2755
+
diff --git a/addons/l10n_in_hr_payroll/views/report_hr_yearly_salary_detail_template.xml b/addons/l10n_in_hr_payroll/views/report_hr_yearly_salary_detail_template.xml
index 8a322cea9bc..3dc77974e37 100644
--- a/addons/l10n_in_hr_payroll/views/report_hr_yearly_salary_detail_template.xml
+++ b/addons/l10n_in_hr_payroll/views/report_hr_yearly_salary_detail_template.xml
@@ -97,7 +97,7 @@
-
+
Deductions:
@@ -110,7 +110,7 @@
-
+
diff --git a/addons/l10n_mx/data/account_tax_data.xml b/addons/l10n_mx/data/account_tax_data.xml
index 168b1955609..ddebf841d68 100644
--- a/addons/l10n_mx/data/account_tax_data.xml
+++ b/addons/l10n_mx/data/account_tax_data.xml
@@ -18,6 +18,10 @@
DIOT: 16%taxes
+
+ DIOT: 16% NO ACREDITABLE
+ taxes
+ DIOT: 16% IMPtaxes
diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js
index 5b1f759b319..141158e11c3 100644
--- a/addons/point_of_sale/static/src/js/chrome.js
+++ b/addons/point_of_sale/static/src/js/chrome.js
@@ -741,7 +741,7 @@ var Chrome = PosBaseWidget.extend({
if(err.message === 'XmlHttpRequestError '){
title = 'Network Failure (XmlHttpRequestError)';
body = 'The Point of Sale could not be loaded due to a network problem.\n Please check your internet connection.';
- }else if(err.message === 'OpenERP Server Error'){
+ }else if(err.code === 200){
title = err.data.message;
body = err.data.debug;
}
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 16582468399..9d73da722ff 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -332,8 +332,8 @@ exports.PosModel = Backbone.Model.extend({
},
},{
model: 'res.currency',
- fields: ['name','symbol','position','rounding'],
- ids: function(self){ return [self.config.currency_id[0]]; },
+ fields: ['name','symbol','position','rounding','rate'],
+ ids: function(self){ return [self.config.currency_id[0], self.company.currency_id[0]]; },
loaded: function(self, currencies){
self.currency = currencies[0];
if (self.currency.rounding > 0 && self.currency.rounding < 1) {
@@ -342,6 +342,7 @@ exports.PosModel = Backbone.Model.extend({
self.currency.decimals = 0;
}
+ self.company_currency = currencies[1];
},
},{
model: 'pos.category',
@@ -360,7 +361,12 @@ exports.PosModel = Backbone.Model.extend({
domain: [['sale_ok','=',true],['available_in_pos','=',true]],
context: function(self){ return { display_default_code: false }; },
loaded: function(self, products){
+ var using_company_currency = self.config.currency_id[0] === self.company.currency_id[0];
+ var conversion_rate = self.currency.rate / self.company_currency.rate;
self.db.add_products(_.map(products, function (product) {
+ if (!using_company_currency) {
+ product.lst_price = round_pr(product.lst_price * conversion_rate, self.currency.rounding);
+ }
product.categ = _.findWhere(self.product_categories, {'id': product.categ_id[0]});
return new exports.Product({}, product);
}));
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index 1b59aa34cba..172bd53a152 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -1281,7 +1281,8 @@ var ClientListScreenWidget = ScreenWidget.extend({
})
.then(function(partner_id){
self.saved_client_details(partner_id);
- },function(type,err){
+ },function(err,ev){
+ ev.preventDefault();
var error_body = _t('Your Internet connection is probably down.');
if (err.data) {
var except = err.data;
diff --git a/addons/point_of_sale/tools/posbox/configuration/connect_to_wifi.sh b/addons/point_of_sale/tools/posbox/configuration/connect_to_wifi.sh
index 681732ae714..8f2d6b567cc 100755
--- a/addons/point_of_sale/tools/posbox/configuration/connect_to_wifi.sh
+++ b/addons/point_of_sale/tools/posbox/configuration/connect_to_wifi.sh
@@ -48,14 +48,18 @@ function connect () {
if [ -z "${PASSWORD}" ] ; then
sudo iwconfig wlan0 essid "${ESSID}"
else
- sudo wpa_passphrase "${ESSID}" "${PASSWORD}" > "${WPA_PASS_FILE}"
+ # Necessary in stretch: https://www.raspberrypi.org/forums/viewtopic.php?t=196927
+ sudo cp /etc/wpa_supplicant/wpa_supplicant.conf "${WPA_PASS_FILE}"
+ chmod 777 ${WPA_PASS_FILE}
+ sudo wpa_passphrase "${ESSID}" "${PASSWORD}" >> "${WPA_PASS_FILE}"
sudo wpa_supplicant -B -i wlan0 -c "${WPA_PASS_FILE}"
fi
+ sudo systemctl daemon-reload
sudo service dhcpcd restart
# give dhcp some time
- timeout 30 sh -c 'until ifconfig wlan0 | grep "inet addr:" ; do sleep 0.1 ; done'
+ timeout 30 sh -c 'until ifconfig wlan0 | grep "inet " ; do sleep 0.1 ; done'
TIMEOUT_RETURN=$?
if [ ${TIMEOUT_RETURN} -eq 124 ] && [ -z "${NO_AP}" ] ; then
diff --git a/addons/point_of_sale/tools/posbox/configuration/keep_wifi_alive.sh b/addons/point_of_sale/tools/posbox/configuration/keep_wifi_alive.sh
index a04aba620d4..5c8a27492bb 100755
--- a/addons/point_of_sale/tools/posbox/configuration/keep_wifi_alive.sh
+++ b/addons/point_of_sale/tools/posbox/configuration/keep_wifi_alive.sh
@@ -2,7 +2,7 @@
CURRENT_WIFI_NETWORK_FILE="/tmp/current_wifi_network.txt"
while true ; do
- if [ -z "$(cat <(ifconfig eth0) <(ifconfig wlan0) | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}';)" ] ; then
+ if [ -z "$(cat <(ifconfig eth0) <(ifconfig wlan0) | grep "inet ";)" ] ; then
ESSID=$(head -n 1 "${CURRENT_WIFI_NETWORK_FILE}" | tr -d '\n')
PASSWORD=$(tail -n 1 "${CURRENT_WIFI_NETWORK_FILE}" | tr -d '\n')
diff --git a/addons/point_of_sale/tools/posbox/configuration/wireless_ap.sh b/addons/point_of_sale/tools/posbox/configuration/wireless_ap.sh
index 97b011b296d..b925dc05cae 100755
--- a/addons/point_of_sale/tools/posbox/configuration/wireless_ap.sh
+++ b/addons/point_of_sale/tools/posbox/configuration/wireless_ap.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
FORCE_HOST_AP="${1}"
-WIRED_IP=$(ifconfig eth0 | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}';)
+WIRED_IP=$(python3 -c "import netifaces as ni; print(ni.ifaddresses('eth0').get(ni.AF_INET) and ni.ifaddresses('eth0')[ni.AF_INET][0]['addr'] or '')")
WIFI_NETWORK_FILE="/home/pi/wifi_network.txt"
# if there is no wired ip, attempt to start an AP through wireless interface
diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/dhcpcd.conf b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/dhcpcd.conf
index 00a1d04f28f..22698cb46ba 100644
--- a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/dhcpcd.conf
+++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/dhcpcd.conf
@@ -39,6 +39,8 @@ slaac private
# A hook script is provided to lookup the hostname if not set by the DHCP
# server, but it should not be run by default.
nohook lookup-hostname
+# stretch: we do not want to start wpa_supplicant as our script starts wpa_supplicant itself
+nohook wpa_supplicant
# dhcpcd will assign zeroconf 169.254.*.* addresses when
# it can't connect, which we don't want
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
index 4ab50a456a1..8cd645c1557 100755
--- a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
@@ -14,7 +14,7 @@
. /lib/lsb/init-functions
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
-DAEMON=/home/pi/odoo/odoo.py
+DAEMON=/home/pi/odoo/odoo-bin
NAME=odoo
DESC=odoo
CONFIG=/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/odoo.conf
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
index 38de23ca312..07e5a6d4c1d 100755
--- a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
@@ -21,11 +21,15 @@ apt-get update && apt-get -y upgrade
# Do not be too fast to upgrade to more recent firmware and kernel than 4.38
# Firmware 4.44 seems to prevent the LED mechanism from working
-PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-tz python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg screen iw hostapd isc-dhcp-server git rsync console-data lightdm xserver-xorg-video-fbdev xserver-xorg-input-evdev iceweasel xdotool unclutter x11-utils openbox python-netifaces rpi-update"
-
+PKGS_TO_INSTALL="adduser postgresql python3 python3-dateutil python3-decorator python3-docutils python3-feedparser python3-pil python3-jinja2 python3-ldap3 python3-lxml python3-mako python3-mock python3-openid python3-psutil python3-psycopg2 python3-babel python3-pydot python3-pyparsing python3-pypdf2 python3-reportlab python3-requests python3-simplejson python3-tz python3-vatnumber python3-werkzeug python3-yaml python3-serial python3-pip python3-dev localepurge vim mc mg screen iw hostapd isc-dhcp-server git rsync console-data lightdm xserver-xorg-video-fbdev xserver-xorg-input-evdev iceweasel xdotool unclutter x11-utils openbox python3-netifaces rpi-update python3-passlib python3-libsass python3-qrcode python3-html2text python3-unittest2 python3-simplejson"
# KEEP OWN CONFIG FILES DURING PACKAGE CONFIGURATION
# http://serverfault.com/questions/259226/automatically-keep-current-version-of-config-files-when-apt-get-install
-apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes install ${PKGS_TO_INSTALL}
+apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install ${PKGS_TO_INSTALL}
+pg_lsclusters
+systemctl start postgresql@9.6-main
+systemctl status postgresql@9.6-main
+
+sudo -u postgres createuser -s pi
apt-get clean
localepurge
@@ -34,23 +38,15 @@ rm -rf /usr/share/doc
# python-usb in wheezy is too old
# the latest pyusb from pip does not work either, usb.core.find() never returns
# this may be fixed with libusb>2:1.0.11-1, but that's the most recent one in raspbian
-# so we install the latest pyusb that works with this libusb
-pip install pyusb==1.0.0b1
-pip install qrcode
-pip install evdev
-pip install simplejson
-pip install unittest2
-
-# --upgrade because websocket_client in wheezy is bad:
-# https://github.com/docker/compose/issues/1288
-pip install --upgrade websocket_client
+# so we install the latest pyusb that works with this libusb.
+# Even in stretch, we had an error with langid (but worked otherwise)
+pip3 install pyusb==1.0.0b1
+pip3 install evdev
groupadd usbusers
usermod -a -G usbusers pi
usermod -a -G lp pi
usermod -a -G input lightdm
-
-sudo -u postgres createuser -s pi
mkdir /var/log/odoo
chown pi:pi /var/log/odoo
chown pi:pi -R /home/pi/odoo/
diff --git a/addons/point_of_sale/tools/posbox/posbox_create_image.sh b/addons/point_of_sale/tools/posbox/posbox_create_image.sh
index ae3f52ceb55..1f2764628d8 100755
--- a/addons/point_of_sale/tools/posbox/posbox_create_image.sh
+++ b/addons/point_of_sale/tools/posbox/posbox_create_image.sh
@@ -28,6 +28,8 @@ __base="$(basename ${__file} .sh)"
MOUNT_POINT="${__dir}/root_mount"
OVERWRITE_FILES_BEFORE_INIT_DIR="${__dir}/overwrite_before_init"
OVERWRITE_FILES_AFTER_INIT_DIR="${__dir}/overwrite_after_init"
+VERSION=11.0
+REPO=https://github.com/odoo/odoo.git
if [ ! -f kernel-qemu ] || ! file_exists *raspbian*.img ; then
./posbox_download_images.sh
@@ -42,14 +44,14 @@ rm -rf "${CLONE_DIR}"
if [ ! -d $CLONE_DIR ]; then
echo "Clone Github repo"
mkdir -p "${CLONE_DIR}"
- git clone -b 8.0 --no-local --no-checkout --depth 1 https://github.com/odoo/odoo.git "${CLONE_DIR}"
+ git clone -b ${VERSION} --no-local --no-checkout --depth 1 ${REPO} "${CLONE_DIR}"
cd "${CLONE_DIR}"
git config core.sparsecheckout true
echo "addons/web
addons/hw_*
addons/point_of_sale/tools/posbox/configuration
-openerp/
-odoo.py" | tee --append .git/info/sparse-checkout > /dev/null
+odoo/
+odoo-bin" | tee --append .git/info/sparse-checkout > /dev/null
git read-tree -mu HEAD
fi
@@ -89,7 +91,7 @@ sleep 5
e2fsck -f "${LOOP_MAPPER_PATH}" # resize2fs requires clean fs
resize2fs "${LOOP_MAPPER_PATH}"
-mkdir "${MOUNT_POINT}"
+mkdir -p "${MOUNT_POINT}" #-p: no error if existing
mount "${LOOP_MAPPER_PATH}" "${MOUNT_POINT}"
# 'overlay' the overwrite directory onto the mounted image filesystem
@@ -107,7 +109,8 @@ umount "${MOUNT_POINT}"
# from http://paulscott.co.za/blog/full-raspberry-pi-raspbian-emulation-with-qemu/
# ssh pi@localhost -p10022
-QEMU_OPTS=(-kernel kernel-qemu -cpu arm1176 -m 256 -M versatilepb -no-reboot -serial stdio -append 'root=/dev/sda2 rootfstype=ext4 rw' -hda posbox.img -net user,hostfwd=tcp::10022-:22,hostfwd=tcp::18069-:8069 -net nic)
+# as of stretch with newer kernels, the versatile-pb.dtb file is necessary
+QEMU_OPTS=(-kernel kernel-qemu -cpu arm1176 -m 256 -M versatilepb -dtb versatile-pb.dtb -no-reboot -serial stdio -append 'root=/dev/sda2 rootfstype=ext4 rw' -hda posbox.img -net user,hostfwd=tcp::10022-:22,hostfwd=tcp::18069-:8069 -net nic)
if [ -z ${DISPLAY:-} ] ; then
QEMU_OPTS+=(-nographic)
fi
diff --git a/addons/point_of_sale/tools/posbox/posbox_download_images.sh b/addons/point_of_sale/tools/posbox/posbox_download_images.sh
index 33beddac041..b13505ce16b 100755
--- a/addons/point_of_sale/tools/posbox/posbox_download_images.sh
+++ b/addons/point_of_sale/tools/posbox/posbox_download_images.sh
@@ -1,5 +1,8 @@
#!/bin/sh
-wget 'http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2017-07-05/2017-07-05-raspbian-jessie-lite.zip' -O raspbian.img.zip
+wget 'http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2018-06-29/2018-06-27-raspbian-stretch-lite.zip' -O raspbian.img.zip
unzip raspbian.img.zip
-wget 'https://github.com/dhruvvyas90/qemu-rpi-kernel/raw/master/kernel-qemu-4.4.13-jessie' -O kernel-qemu
+wget 'https://github.com/dhruvvyas90/qemu-rpi-kernel/raw/master/kernel-qemu-4.9.59-stretch' -O kernel-qemu
+wget 'https://github.com/dhruvvyas90/qemu-rpi-kernel/raw/master/versatile-pb.dtb'
+
+
diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py
index ffa74c60037..7b6fa10d40a 100644
--- a/addons/product/models/product_template.py
+++ b/addons/product/models/product_template.py
@@ -271,7 +271,8 @@ class ProductTemplate(models.Model):
@api.one
@api.depends('product_variant_ids.product_tmpl_id')
def _compute_product_variant_count(self):
- self.product_variant_count = len(self.product_variant_ids)
+ # do not pollute variants to be prefetched when counting variants
+ self.product_variant_count = len(self.with_prefetch().product_variant_ids)
@api.depends('product_variant_ids', 'product_variant_ids.default_code')
def _compute_default_code(self):
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index 1a5356a8ead..ebd17d8b02f 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -595,7 +595,9 @@ class ProductUoM(models.Model):
if 'factor' in values or 'factor_inv' in values or 'category_id' in values:
changed = self.filtered(
lambda u: any(u[f] != values[f] if f in values else False
- for f in {'factor', 'factor_inv', 'category_id'}))
+ for f in {'factor', 'factor_inv'})) + self.filtered(
+ lambda u: any(u[f].id != int(values[f]) if f in values else False
+ for f in {'category_id'}))
if changed:
stock_move_lines = self.env['stock.move.line'].search_count([
('product_uom_id.category_id', 'in', changed.mapped('category_id.id')),
diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py
index 6e3189ed168..578d0113697 100644
--- a/addons/stock_account/models/product.py
+++ b/addons/stock_account/models/product.py
@@ -347,7 +347,8 @@ class ProductProduct(models.Model):
qty_delivered += qty_to_consider
# `move.price_unit` is negative if the move is out and positive if the move is
# dropshipped. Use its absolute value to compute the average price unit.
- average_price_unit = (average_price_unit * (qty_delivered - qty_to_consider) + abs(move.price_unit) * qty_to_consider) / qty_delivered
+ if qty_delivered:
+ average_price_unit = (average_price_unit * (qty_delivered - qty_to_consider) + abs(move.price_unit) * qty_to_consider) / qty_delivered
if qty_delivered == quantity:
break
return average_price_unit
diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py
index af38ff50027..7067c7444da 100644
--- a/addons/stock_account/models/stock.py
+++ b/addons/stock_account/models/stock.py
@@ -151,7 +151,7 @@ class StockMove(models.Model):
def _get_price_unit(self):
""" Returns the unit price to store on the quant """
- return self.price_unit or self.product_id.standard_price
+ return not self.company_id.currency_id.is_zero(self.price_unit) and self.price_unit or self.product_id.standard_price
@api.model
def _get_in_base_domain(self, company_id=False):
diff --git a/addons/web/static/src/js/_deprecated/data.js b/addons/web/static/src/js/_deprecated/data.js
index eb70f330076..9f8115561b2 100644
--- a/addons/web/static/src/js/_deprecated/data.js
+++ b/addons/web/static/src/js/_deprecated/data.js
@@ -260,14 +260,15 @@ var QueryGroup = Class.extend({
var count_key = (grouping_fields[0] && grouping_fields[0].split(':')[0]) + '_count';
var aggregates = {};
- _(fixed_group).each(function (value, key) {
- if (key.indexOf('__') === 0
+ for (var key in fixed_group) {
+ if (fixed_group.hasOwnProperty(key)) {
+ if (!(key.indexOf('__') === 0
|| _.contains(grouping_fields, key)
- || (key === count_key)) {
- return;
+ || (key === count_key))) {
+ aggregates[key] = fixed_group[key] || 0;
+ }
}
- aggregates[key] = value || 0;
- });
+ }
this.model = new Model(
model, fixed_group.__context, fixed_group.__domain);
diff --git a/addons/website_event/models/event.py b/addons/website_event/models/event.py
index cf5c2bbd872..25d2c5cf69f 100644
--- a/addons/website_event/models/event.py
+++ b/addons/website_event/models/event.py
@@ -67,8 +67,8 @@ class Event(models.Model):
if not event.menu_id:
root_menu = self.env['website.menu'].create({'name': event.name})
event.menu_id = root_menu
- for sequence, (name, url, xml_id) in enumerate(self._get_menu_entries()):
- self._create_menu(sequence, name, url, xml_id)
+ for sequence, (name, url, xml_id) in enumerate(event._get_menu_entries()):
+ event._create_menu(sequence, name, url, xml_id)
return res
def _create_menu(self, sequence, name, url, xml_id):
diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py
index c10a1d9efc4..3b2f852f066 100644
--- a/addons/website_forum/models/forum.py
+++ b/addons/website_forum/models/forum.py
@@ -497,7 +497,7 @@ class Post(models.Model):
self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accept * mult)
if 'tag_ids' in vals:
tag_ids = set(tag.get('id') for tag in self.resolve_2many_commands('tag_ids', vals['tag_ids']))
- if any(set(post.tag_ids) != tag_ids for post in self) and any(self.env.user.karma < post.forum_id.karma_edit_retag for post in self):
+ if any(set(post.tag_ids.ids) != tag_ids for post in self) and any(self.env.user.karma < post.forum_id.karma_edit_retag for post in self):
raise KarmaError(_('Not enough karma to retag.'))
if any(key not in trusted_keys for key in vals) and any(not post.can_edit for post in self):
raise KarmaError('Not enough karma to edit a post.')
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index 99e0be2b84d..9e0f412c0b2 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -622,7 +622,7 @@
-
+
diff --git a/doc/cla/individual/rwfnf.md b/doc/cla/individual/rwfnf.md
new file mode 100644
index 00000000000..0e92424bf9f
--- /dev/null
+++ b/doc/cla/individual/rwfnf.md
@@ -0,0 +1,9 @@
+United Kingdom, 2018-07-15
+
+I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this declaration.
+
+Signed,
+
+Orya Blumeneld orya@rawrfoods.com https://github.com/rawrfnf
diff --git a/doc/reference/cmdline.rst b/doc/reference/cmdline.rst
index 12cd5f7a9d8..058eac51ddd 100644
--- a/doc/reference/cmdline.rst
+++ b/doc/reference/cmdline.rst
@@ -384,13 +384,6 @@ customize the amount of logging output
In case of conflict between :option:`--log-level` and
:option:`--log-handler`, the latter is used
-translations
-------------
-
-.. option:: --i18n-import
-
-.. option:: --i18n-export
-
emails
------
diff --git a/doc/reference/guidelines.rst b/doc/reference/guidelines.rst
index ae0ab00555d..ec7e3c98cbe 100644
--- a/doc/reference/guidelines.rst
+++ b/doc/reference/guidelines.rst
@@ -222,7 +222,6 @@ Use the following pattern :
*concerned_group* is the short name of the concerned group ('user'
for the 'model_name_group_user', 'public' for public user, 'company'
for multi-company rules, ...).
-* For a group : :samp:`{}_group_{}` where *group_name* is the name of the group, generally 'user', 'manager', ...
.. code-block:: xml
diff --git a/doc/setup.rst b/doc/setup.rst
index f200108964e..99b4bd3ad51 100644
--- a/doc/setup.rst
+++ b/doc/setup.rst
@@ -9,5 +9,6 @@ Setting Up
setup/install
setup/deploy
+ setup/update
setup/cdn
setup/enterprise
diff --git a/doc/setup/install.rst b/doc/setup/install.rst
index 90e6e4ae089..c1f65abafd1 100644
--- a/doc/setup/install.rst
+++ b/doc/setup/install.rst
@@ -6,7 +6,7 @@
Installing Odoo
===============
-There are mutliple ways to install Odoo, or not install it at all, depending
+There are multiple ways to install Odoo, or not install it at all, depending
on the intended use case.
This documents attempts to describe most of the installation options.
@@ -588,7 +588,7 @@ Docker
======
The full documentation on how to use Odoo with Docker can be found on the
-offcial Odoo `docker image `_ page.
+official Odoo `docker image `_ page.
.. _demo: https://demo.odoo.com
.. _docker: https://www.docker.com
diff --git a/doc/setup/update.rst b/doc/setup/update.rst
new file mode 100644
index 00000000000..7c7b5c06227
--- /dev/null
+++ b/doc/setup/update.rst
@@ -0,0 +1,151 @@
+:banner: banners/installing_odoo.jpg
+
+.. _setup/update:
+
+=============================
+Updating an Odoo installation
+=============================
+
+Introduction
+============
+
+In order to benefit from the latest improvements, security fixes, bug corrections and
+performance boosts, you may need to update your Odoo installation from time to time.
+
+This guide only applies when are using Odoo on your own hosting infrastructure.
+If you are using one of the Odoo Cloud solutions, updates are automatically performed for you.
+
+The terminology surrounding software updates is often confusing, so here are some preliminary
+definitions:
+
+Updating (an Odoo installation)
+ Refers to the process of obtaining the latest revision of the source code for
+ your current Odoo Edition. For example, updating your Odoo Enterprise 11.0 to the
+ latest revision.
+ This does not directly cause any change to the contents of your Odoo database, and
+ can be undone by reinstalling the previous revision of the source code.
+
+Upgrading (an Odoo database)
+ Refers to a complex data processing operation where the structure and contents of your
+ database is permanently altered to make it compatible with a new release of Odoo.
+ This operation is irreversible and typically accomplished via Odoo's
+ `database upgrade service `_, when you decide to
+ switch to a newer release of Odoo.
+ Historically, this process has also been known as a "migration" because it involves moving data
+ around inside the database, even though the database may end up at the same physical location
+ after the upgrade.
+
+This page describes the typical steps needed to *update* an Odoo installation to the latest
+version. If you'd like more information about upgrading a database, please visit the
+`Odoo Upgrade page `_ instead.
+
+
+In a nutshell
+=============
+
+Updating Odoo is accomplished by simply reinstalling the latest version of your Odoo
+Edition on top of your current installation. This will preserve your data without any alteration,
+as long as you do not uninstall PostgreSQL (the database engine that comes with Odoo).
+
+The main reference for updating is logically our :ref:`installation guide `,
+which explains the common installation methods.
+
+Updating is also most appropriately accomplished by the person who deployed Odoo initially,
+because the procedure is very similar.
+
+.. note:: We always recommend to download a complete new up-to-date Odoo version, rather than
+ manually applying patches, such as the security patches that come with Security
+ Advisories.
+ The patches are mainly provided for installations that are heavily customized, or for
+ technical personnel who prefer to apply minimal changes temporarily while testing a
+ complete update.
+
+
+Step 1: Download an updated Odoo version
+========================================
+
+The central download page is https://www.odoo.com/page/download. If you see a "Buy" link for the
+Odoo Enterprise download, make sure you are logged into Odoo.com with the same login that is
+linked to your Odoo Enterprise subscription.
+
+Alternatively, you can use the unique download link that was included with your Odoo Enterprise
+purchase confirmation email.
+
+.. note:: Downloading an updated version is not necessary if you installed via Github (see below)
+
+
+Step 2: Make a backup of your database
+======================================
+
+The update procedure is quite safe and should not alter you data. However it's always best to take
+a full database backup before performing any change on your installation, and to store it somewhere
+safe, on a different computer.
+
+If you have not disabled the database manager screen (see :ref:`here ` why you should), you
+can use it (link at bottom of your database selection screen) to download a backup of your
+database(s). If you disabled it, use the same procedure than for your usual backups.
+
+
+Step 3: Install the updated version
+===================================
+
+Choose the method that matches your current installation:
+
+
+Packaged Installers
+-------------------
+
+If you installed Odoo with an installation package downloaded on our website (the recommended method),
+updating is very simple.
+All you have to do is download the installation package corresponding to your system (see step #1)
+and install it on your server. They are updated daily and include the latest security fixes.
+Usually, you can simply double-click the package to install it on top of the current installation.
+After installing the package, be sure to restart the Odoo service or reboot your server,
+and you're all set.
+
+Source Install (Tarball)
+------------------------
+If you have originally installed Odoo with the "tarball" version (source code archive), you have
+to replace the installation directory with a newer version. First download the latest tarball
+from Odoo.com. They are updated daily and include the latest security fixes (see step #1)
+After downloading the package, extract it to a temporary location on your server.
+
+You will get a folder labelled with the version of the source code, for example "odoo-11.0+e.20180719",
+that contains a folder "odoo.egg-info" and the actual source code folder named "odoo" (for Odoo 10
+and later) or "openerp" for older versions.
+You can ignore the odoo.egg-info folder. Locate the folder where your current installation is deployed,
+and replace it with the newer "odoo" or "openerp" folder that was in the archive you just extracted.
+
+Be sure to match the folder layout, for example the new "addons" folder included in the source code
+should end up exactly at the same path it was before. Next, watch out for any specific configuration
+files that you may have manually copied or modified in the old folder, and copy them over to the
+new folder.
+Finally, restart the Odoo service or reboot the machine, and you are all set.
+
+Source Install (Github)
+-----------------------
+If you have originally installed Odoo with a full Github clone of the official repositories, the
+update procedure requires you to pull the latest source code via git.
+Change into the directory for each repository (the main Odoo repository, and the Enterprise
+repository), and run the following commands::
+
+ git fetch
+ git rebase --autostash
+
+The last command may encounter source code conflicts if you had edited the Odoo source code locally.
+The error message will give you the list of files with conflicts, and you will need to resolve
+the conflicts manually, by editing them and deciding which part of the code to keep.
+
+Alternatively, if you prefer to simply discard the conflicting changes and restore the official
+version, you can use the following command::
+
+ git reset --hard
+
+Finally, restart the Odoo service or reboot the machine, and you should be done.
+
+
+Docker
+------
+
+Please refer to our `Docker image documentation `_ for
+specific update instructions.
\ No newline at end of file
diff --git a/odoo/addons/base/models/assetsbundle.py b/odoo/addons/base/models/assetsbundle.py
index c59d5980a97..f7cf8fd962e 100644
--- a/odoo/addons/base/models/assetsbundle.py
+++ b/odoo/addons/base/models/assetsbundle.py
@@ -102,8 +102,11 @@ class AssetsBundle(object):
self.javascripts.append(JavascriptAsset(self, url=f['url'], filename=f['filename'], inline=f['content']))
# depreciated and will remove after v11
- def to_html(self, sep=None, css=True, js=True, debug=False, async=False, url_for=(lambda url: url)):
- nodes = self.to_node(css=css, js=js, debug=debug, async=async)
+ def to_html(self, sep=None, css=True, js=True, debug=False, async_load=False, url_for=(lambda url: url), **kw):
+ if 'async' in kw:
+ _logger.warning("Using deprecated argument 'async' in to_html call, use 'async_load' instead.")
+ async_load = kw['async']
+ nodes = self.to_node(css=css, js=js, debug=debug, async_load=async_load)
if sep is None:
sep = u'\n '
@@ -121,10 +124,13 @@ class AssetsBundle(object):
return sep + sep.join(response)
- def to_node(self, css=True, js=True, debug=False, async=False):
+ def to_node(self, css=True, js=True, debug=False, async_load=False, **kw):
"""
:returns [(tagName, attributes, content)] if the tag is auto close
"""
+ if 'async' in kw:
+ _logger.warning("Using deprecated argument 'async' in to_node call, use 'async_load' instead.")
+ async_load = kw['async']
response = []
if debug == 'assets':
if css and self.stylesheets:
@@ -157,7 +163,7 @@ class AssetsBundle(object):
response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node())
if js and self.javascripts:
attr = OrderedDict([
- ["async", "async" if async else None],
+ ["async", "async" if async_load else None],
["type", "text/javascript"],
["src", self.js().url],
])
diff --git a/odoo/addons/base/models/ir_qweb.py b/odoo/addons/base/models/ir_qweb.py
index b7dc631a2dc..e6ab6c6611e 100644
--- a/odoo/addons/base/models/ir_qweb.py
+++ b/odoo/addons/base/models/ir_qweb.py
@@ -278,24 +278,28 @@ class IrQWeb(models.AbstractModel, QWeb):
# compatibility to remove after v11 - DEPRECATED
@tools.conditional(
'xml' not in tools.config['dev_mode'],
- tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', 'css', 'js', 'debug', 'async', keys=("website_id",)),
+ tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', 'css', 'js', 'debug', 'kw.get("async")', 'async_load', keys=("website_id",)),
)
- def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
+ def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async_load=False, values=None, **kw):
+ if 'async' in kw:
+ async_load = kw['async']
files, remains = self._get_asset_content(xmlid, options)
asset = self.get_asset_bundle(xmlid, files, remains, env=self.env)
- return asset.to_html(css=css, js=js, debug=debug, async=async, url_for=(values or {}).get('url_for', lambda url: url))
+ return asset.to_html(css=css, js=js, debug=debug, async_load=async_load, url_for=(values or {}).get('url_for', lambda url: url))
@tools.conditional(
# in non-xml-debug mode we want assets to be cached forever, and the admin can force a cache clear
# by restarting the server after updating the source code (or using the "Clear server cache" in debug tools)
'xml' not in tools.config['dev_mode'],
- tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', 'css', 'js', 'debug', 'async', keys=("website_id",)),
+ tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', 'css', 'js', 'debug', 'kw.get("async")', 'async_load', keys=("website_id",)),
)
- def _get_asset_nodes(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
+ def _get_asset_nodes(self, xmlid, options, css=True, js=True, debug=False, async_load=False, values=None, **kw):
+ if 'async' in kw:
+ async_load = kw['async']
files, remains = self._get_asset_content(xmlid, options)
asset = self.get_asset_bundle(xmlid, files, env=self.env)
remains = [node for node in remains if (css and node[0] == 'link') or (js and node[0] != 'link')]
- return remains + asset.to_node(css=css, js=js, debug=debug, async=async)
+ return remains + asset.to_node(css=css, js=js, debug=debug, async_load=async_load)
@tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', keys=("website_id",))
def _get_asset_content(self, xmlid, options):
diff --git a/odoo/addons/base/models/ir_translation.py b/odoo/addons/base/models/ir_translation.py
index 1cc17189323..0ad132d4c33 100644
--- a/odoo/addons/base/models/ir_translation.py
+++ b/odoo/addons/base/models/ir_translation.py
@@ -470,6 +470,7 @@ class IrTranslation(models.Model):
discarded += trans
else:
trans.write({'src': matches[0], 'state': trans.state})
+ trans_src.append(matches[0]) # avoid reuse of term
else:
outdated += trans
diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py
index 7f5d6e35e71..7a5ec03707a 100644
--- a/odoo/tools/safe_eval.py
+++ b/odoo/tools/safe_eval.py
@@ -101,6 +101,8 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
'CALL_FUNCTION_EX',
# Already in P2 but apparently the first one is used more aggressively in P3
'CALL_FUNCTION_KW', 'CALL_FUNCTION_VAR', 'CALL_FUNCTION_VAR_KW',
+ # Added in P3.7 https://bugs.python.org/issue26110
+ 'CALL_METHOD', 'LOAD_METHOD',
'GET_ITER', 'FOR_ITER', 'YIELD_VALUE',
'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE',
# New in Python 2.7 - http://bugs.python.org/issue4715 :