diff --git a/addons/account/report/account_report.py b/addons/account/report/account_report.py index aa2d5c1568f..63990762b40 100644 --- a/addons/account/report/account_report.py +++ b/addons/account/report/account_report.py @@ -94,7 +94,7 @@ class report_aged_receivable(osv.osv): self._init(cr, user) self.called = True # To make sure that init doesn't get called multiple times - res = super(report_aged_receivable, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu) + res = super(report_aged_receivable, self).fields_view_get(cr, user, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu) return res def _calc_bal(self, cr, uid, ids, name, args, context=None): diff --git a/addons/account/static/src/js/account_widgets.js b/addons/account/static/src/js/account_widgets.js index 770989db6f0..a15b2c05e64 100644 --- a/addons/account/static/src/js/account_widgets.js +++ b/addons/account/static/src/js/account_widgets.js @@ -1214,9 +1214,11 @@ openerp.account = function (instance) { changePartnerClickHandler: function() { var self = this; + var partner_name = self.st_line.partner_name; $.when(self.changePartner(false)).then(function(){ self.$(".change_partner_container").show(); self.$(".partner_name").hide(); + self.$(".change_partner_container").find("input").attr("placeholder", partner_name); self.change_partner_field.$drop_down.trigger("click"); }) }, @@ -1658,10 +1660,9 @@ openerp.account = function (instance) { // Update model .call("write", [[self.st_line_id], {'partner_id': partner_id}]) .then(function () { - self.do_load_reconciliation_proposition = false; // of the server might set the statement line's partner + self.do_load_reconciliation_proposition = true; // the server will set the statement line with the new partner self.animation_speed = 0; return $.when(self.restart(self.get("mode"))).then(function(){ - self.do_load_reconciliation_proposition = true; self.is_consistent = true; self.set("mode", "match"); }); diff --git a/addons/account/views/report_overdue.xml b/addons/account/views/report_overdue.xml index df3934ff0de..e6ec90509da 100644 --- a/addons/account/views/report_overdue.xml +++ b/addons/account/views/report_overdue.xml @@ -20,7 +20,7 @@

- +

diff --git a/addons/account_analytic_plans/account_analytic_plans.py b/addons/account_analytic_plans/account_analytic_plans.py index 88f58d317a3..21a314f77b0 100644 --- a/addons/account_analytic_plans/account_analytic_plans.py +++ b/addons/account_analytic_plans/account_analytic_plans.py @@ -169,7 +169,7 @@ class account_analytic_plan_instance(osv.osv): if context is None: context = {} wiz_id = self.pool.get('ir.actions.act_window').search(cr, uid, [("name","=","analytic.plan.create.model.action")], context=context) - res = super(account_analytic_plan_instance,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar=toolbar, submenu=submenu) + res = super(account_analytic_plan_instance,self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu) journal_obj = self.pool.get('account.journal') analytic_plan_obj = self.pool.get('account.analytic.plan') if (res['type']=='form'): @@ -361,7 +361,7 @@ class account_move_line(osv.osv): def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): if context is None: context = {} - result = super(account_move_line, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar=toolbar, submenu=submenu) + result = super(account_move_line, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu) return result diff --git a/addons/anonymization/anonymization.py b/addons/anonymization/anonymization.py index a114c7712ff..192cca79653 100644 --- a/addons/anonymization/anonymization.py +++ b/addons/anonymization/anonymization.py @@ -311,7 +311,7 @@ class ir_model_fields_anonymize_wizard(osv.osv_memory): step = context.get('step', 'new_window') - res = super(ir_model_fields_anonymize_wizard, self).fields_view_get(cr, uid, view_id, view_type, context, *args, **kwargs) + res = super(ir_model_fields_anonymize_wizard, self).fields_view_get(cr, uid, view_id, view_type, context=context, *args, **kwargs) eview = etree.fromstring(res['arch']) placeholder = eview.xpath("group[@name='placeholder1']") diff --git a/addons/auth_signup/views/auth_signup_login.xml b/addons/auth_signup/views/auth_signup_login.xml index 0a4ddfc1b30..272c3ac9a08 100644 --- a/addons/auth_signup/views/auth_signup_login.xml +++ b/addons/auth_signup/views/auth_signup_login.xml @@ -40,7 +40,9 @@
- + + +

diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index d92bd493530..c0dc35ef082 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -143,7 +143,7 @@ class crm_lead(format_address, osv.osv): action = self.get_formview_action(cr, user, context['opportunity_id'], context=context) if action.get('views') and any(view_id for view_id in action['views'] if view_id[1] == view_type): view_id = next(view_id[0] for view_id in action['views'] if view_id[1] == view_type) - res = super(crm_lead, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu) + res = super(crm_lead, self).fields_view_get(cr, user, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu) if view_type == 'form': res['arch'] = self.fields_view_get_address(cr, user, res['arch'], context=context) return res diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py index 48400159dd5..7feab491ea1 100644 --- a/addons/gamification/models/challenge.py +++ b/addons/gamification/models/challenge.py @@ -381,6 +381,9 @@ class gamification_challenge(osv.Model): Create goals that haven't been created yet (eg: if added users) Recompute the current value for each goal related""" + goal_obj = self.pool['gamification.goal'] + goal_ids = goal_obj.search(cr, uid, [('challenge_id', 'in', ids), ('state', '=', 'inprogress')], context=context) + goal_obj.unlink(cr, uid, goal_ids, context=context) return self._update_all(cr, uid, ids=ids, context=context) def action_report_progress(self, cr, uid, ids, context=None): diff --git a/addons/google_drive/google_drive.py b/addons/google_drive/google_drive.py index b26cc44860e..45eca296175 100644 --- a/addons/google_drive/google_drive.py +++ b/addons/google_drive/google_drive.py @@ -81,7 +81,7 @@ class config(osv.Model): client_secret=google_drive_client_secret, grant_type="refresh_token", scope=scope or 'https://www.googleapis.com/auth/drive')) - headers = {"Content-type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip, deflate"} + headers = {"Content-type": "application/x-www-form-urlencoded"} try: req = urllib2.Request('https://accounts.google.com/o/oauth2/token', data, headers) content = urllib2.urlopen(req, timeout=TIMEOUT).read() @@ -101,7 +101,7 @@ class config(osv.Model): access_token = self.get_access_token(cr, uid, context=context) # Copy template in to drive with help of new access token request_url = "https://www.googleapis.com/drive/v2/files/%s?fields=parents/id&access_token=%s" % (template_id, access_token) - headers = {"Content-type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip, deflate"} + headers = {"Content-type": "application/x-www-form-urlencoded"} try: req = urllib2.Request(request_url, None, headers) parents = urllib2.urlopen(req, timeout=TIMEOUT).read() diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index 083e5cd5557..0b0f58027ab 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -52,60 +52,37 @@ class EscposDriver(Thread): self.lock = Lock() self.status = {'status':'connecting', 'messages':[]} - def supported_devices(self): - if not os.path.isfile('escpos_devices.pickle'): - return supported_devices.device_list - else: - try: - f = open('escpos_devices.pickle','r') - return pickle.load(f) - f.close() - except Exception as e: - self.set_status('error',str(e)) - return supported_devices.device_list - - def add_supported_device(self,device_string): - r = re.compile('[0-9A-Fa-f]{4}:[0-9A-Fa-f]{4}'); - match = r.search(device_string) - if match: - match = match.group().split(':') - vendor = int(match[0],16) - product = int(match[1],16) - name = device_string.split('ID') - if len(name) >= 2: - name = name[1] - else: - name = name[0] - _logger.info('ESC/POS: adding support for device: '+match[0]+':'+match[1]+' '+name) - - device_list = supported_devices.device_list[:] - if os.path.isfile('escpos_devices.pickle'): - try: - f = open('escpos_devices.pickle','r') - device_list = pickle.load(f) - f.close() - except Exception as e: - self.set_status('error',str(e)) - device_list.append({ - 'vendor': vendor, - 'product': product, - 'name': name, - }) - - try: - f = open('escpos_devices.pickle','w+') - f.seek(0) - pickle.dump(device_list,f) - f.close() - except Exception as e: - self.set_status('error',str(e)) - def connected_usb_devices(self): connected = [] - - for device in self.supported_devices(): - if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None: - connected.append(device) + + # printers can either define bDeviceClass=7, or they can define one of + # their interfaces with bInterfaceClass=7. This class checks for both. + class FindUsbClass(object): + def __init__(self, usb_class): + self._class = usb_class + def __call__(self, device): + # first, let's check the device + if device.bDeviceClass == self._class: + return True + # transverse all devices and look through their interfaces to + # find a matching class + for cfg in device: + intf = usb.util.find_descriptor(cfg, bInterfaceClass=self._class) + + if intf is not None: + return True + + return False + + printers = usb.core.find(find_all=True, custom_match=FindUsbClass(7)) + + for printer in printers: + connected.append({ + 'vendor': printer.idVendor, + 'product': printer.idProduct, + 'name': usb.util.get_string(printer, 256, printer.iManufacturer) + " " + usb.util.get_string(printer, 256, printer.iProduct) + }) + return connected def lockedstart(self): @@ -377,18 +354,3 @@ class EscposProxy(hw_proxy.Proxy): _logger.info('ESC/POS: PRINT XML RECEIPT') driver.push_task('xml_receipt',receipt) - @http.route('/hw_proxy/escpos/add_supported_device', type='http', auth='none', cors='*') - def add_supported_device(self, device_string): - _logger.info('ESC/POS: ADDED NEW DEVICE:'+device_string) - driver.add_supported_device(device_string) - return "The device:\n"+device_string+"\n has been added to the list of supported devices.
Ok" - - @http.route('/hw_proxy/escpos/reset_supported_devices', type='http', auth='none', cors='*') - def reset_supported_devices(self): - try: - os.remove('escpos_devices.pickle') - except Exception as e: - pass - return 'The list of supported devices has been reset to factory defaults.
Ok' - - diff --git a/addons/hw_escpos/escpos/__init__.py b/addons/hw_escpos/escpos/__init__.py index 3fdeddee28b..22a5af61029 100644 --- a/addons/hw_escpos/escpos/__init__.py +++ b/addons/hw_escpos/escpos/__init__.py @@ -1 +1 @@ -__all__ = ["constants","escpos","exceptions","printer","supported_devices"] +__all__ = ["constants","escpos","exceptions","printer"] diff --git a/addons/hw_escpos/escpos/supported_devices.py b/addons/hw_escpos/escpos/supported_devices.py deleted file mode 100644 index ad086b5516b..00000000000 --- a/addons/hw_escpos/escpos/supported_devices.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/python - -# This is a list of esc/pos compatible usb printers. The vendor and product ids can be found by -# typing lsusb in a linux terminal, this will give you the ids in the form ID VENDOR:PRODUCT - -device_list = [ - { 'vendor' : 0x04b8, 'product' : 0x0e03, 'name' : 'Epson TM-T20' }, - { 'vendor' : 0x04b8, 'product' : 0x0202, 'name' : 'Epson TM-T70' }, - { 'vendor' : 0x04b8, 'product' : 0x0e15, 'name' : 'Epson TM-T20II' }, -] diff --git a/addons/hw_posbox_upgrade/controllers/main.py b/addons/hw_posbox_upgrade/controllers/main.py index 5ed958d020e..42811820e74 100644 --- a/addons/hw_posbox_upgrade/controllers/main.py +++ b/addons/hw_posbox_upgrade/controllers/main.py @@ -6,6 +6,7 @@ import time import openerp import openerp.addons.hw_proxy.controllers.main as hw_proxy import threading +import subprocess from openerp import http from openerp.http import request from openerp.tools.translate import _ @@ -74,6 +75,14 @@ upgrade_template = """

To upgrade the posbox, click on the upgrade button. The upgrade will take a few minutes. Do not reboot the PosBox during the upgrade.

+

+ Latest patch: +

+
+"""
+upgrade_template += subprocess.check_output("git --work-tree=/home/pi/odoo/ --git-dir=/home/pi/odoo/.git log -1", shell=True).replace("\n", "
") +upgrade_template += """ +
@@ -95,7 +104,7 @@ class PosboxUpgrader(hw_proxy.Proxy): def perform_upgrade(self): self.upgrading.acquire() - os.system('/home/pi/posbox_update.sh') + os.system('/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/posbox_update.sh') self.upgrading.release() return 'SUCCESS' diff --git a/addons/membership/membership.py b/addons/membership/membership.py index 33128a31800..cc3c1b2c908 100644 --- a/addons/membership/membership.py +++ b/addons/membership/membership.py @@ -427,8 +427,7 @@ class Partner(osv.osv): 'fiscal_position': fpos_id or False }, context=context) line_value['invoice_id'] = invoice_id - invoice_line_id = invoice_line_obj.create(cr, uid, line_value, context=context) - invoice_obj.write(cr, uid, invoice_id, {'invoice_line': [(6, 0, [invoice_line_id])]}, context=context) + invoice_line_obj.create(cr, uid, line_value, context=context) invoice_list.append(invoice_id) if line_value['invoice_line_tax_id']: tax_value = invoice_tax_obj.compute(cr, uid, invoice_id).values() @@ -456,7 +455,7 @@ class Product(osv.osv): view_id = dict_model['membership_products_form'] else: view_id = dict_model['membership_products_tree'] - return super(Product,self).fields_view_get(cr, user, view_id, view_type, context, toolbar, submenu) + return super(Product,self).fields_view_get(cr, user, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu) '''Product''' _inherit = 'product.template' diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/led_status.sh b/addons/point_of_sale/tools/posbox/configuration/led_status.sh similarity index 100% rename from addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/led_status.sh rename to addons/point_of_sale/tools/posbox/configuration/led_status.sh diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/odoo.conf b/addons/point_of_sale/tools/posbox/configuration/odoo.conf similarity index 100% rename from addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/odoo.conf rename to addons/point_of_sale/tools/posbox/configuration/odoo.conf diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/posbox_update.sh b/addons/point_of_sale/tools/posbox/configuration/posbox_update.sh similarity index 100% rename from addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/posbox_update.sh rename to addons/point_of_sale/tools/posbox/configuration/posbox_update.sh diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/setup_ramdisks.sh b/addons/point_of_sale/tools/posbox/configuration/setup_ramdisks.sh similarity index 100% rename from addons/point_of_sale/tools/posbox/overwrite_after_init/etc/setup_ramdisks.sh rename to addons/point_of_sale/tools/posbox/configuration/setup_ramdisks.sh diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/init.d/rcS b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/init.d/rcS index 6380e949834..4d32107fac8 100755 --- a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/init.d/rcS +++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/init.d/rcS @@ -5,6 +5,6 @@ # Call all S??* scripts in /etc/rcS.d/ in numerical/alphabetical order # -/etc/setup_ramdisks.sh +/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/setup_ramdisks.sh exec /etc/init.d/rc S diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/rc.local b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/rc.local index 065b6a05f77..dab16e3b415 100755 --- a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/rc.local +++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/rc.local @@ -20,6 +20,6 @@ fi mkdir -p /var/run/odoo chown pi:pi /var/run/odoo -/home/pi/led_status.sh & +/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/led_status.sh & exit 0 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 9bdaa86c584..f9bc0400abc 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 @@ -17,7 +17,7 @@ PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin DAEMON=/home/pi/odoo/odoo.py NAME=odoo DESC=odoo -CONFIG=/home/pi/odoo.conf +CONFIG=/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/odoo.conf LOGFILE=/var/log/odoo/odoo-server.log PIDFILE=/var/run/${NAME}.pid USER=pi 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 781b599d51f..6838f7fa78c 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 @@ -12,7 +12,7 @@ __base="$(basename ${__file} .sh)" export DEBIAN_FRONTEND=noninteractive # GUI-related packages -PKGS_TO_DELETE="xserver-xorg-video-fbdev xserver-xorg xinit gstreamer1.0-x gstreamer1.0-omx gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-alsa gstreamer1.0-libav epiphany-browser lxde lxtask menu-xdg gksu xserver-xorg-video-fbturbo xpdf gtk2-engines alsa-utils netsurf-gtk zenity desktop-base lxpolkit weston omxplayer raspberrypi-artwork lightdm gnome-themes-standard-data gnome-icon-theme qt50-snapshot qt50-quick-particle-examples idle python-pygame python-tk idle3 python-serial python-picamera debian-reference-en dillo x2x scratch nuscratch raspberrypi-ui-mods timidity smartsim penguinspuzzle pistore sonic-pi python-pifacecommon python-pifacedigitalio oracle-java8-jdk minecraft-pi python-minecraftpi wolfram-engine raspi-config libgl1-mesa-dri libicu48 pypy-upstream lxde-icon-theme python3" +PKGS_TO_DELETE="xserver-xorg-video-fbdev xserver-xorg xinit gstreamer1.0-x gstreamer1.0-omx gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-alsa gstreamer1.0-libav epiphany-browser lxde lxtask menu-xdg gksu xserver-xorg-video-fbturbo xpdf gtk2-engines alsa-utils netsurf-gtk zenity desktop-base lxpolkit weston omxplayer raspberrypi-artwork lightdm gnome-themes-standard-data gnome-icon-theme qt50-snapshot qt50-quick-particle-examples idle python-pygame python-tk idle3 python-serial python-picamera debian-reference-en dillo x2x scratch nuscratch raspberrypi-ui-mods timidity smartsim penguinspuzzle pistore sonic-pi python-pifacecommon python-pifacedigitalio oracle-java8-jdk minecraft-pi python-minecraftpi wolfram-engine raspi-config libgl1-mesa-dri libicu48 pypy-upstream lxde-icon-theme python3 avahi-daemon" INSTALLED_PKGS_TO_DELETE="" set +o errexit for CURRENT_PKG in $(echo $PKGS_TO_DELETE); do @@ -56,6 +56,12 @@ sudo -u postgres createuser -s pi mkdir /var/log/odoo chown pi:pi /var/log/odoo +# logrotate is very picky when it comes to file permissions +chown -R root:root /etc/logrotate.d/ +chmod -R 644 /etc/logrotate.d/ +chown root:root /etc/logrotate.conf +chmod 644 /etc/logrotate.conf + echo "* * * * * rm /var/run/odoo/sessions/*" | crontab - update-rc.d odoo defaults diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.d/rsyslog b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.d/rsyslog new file mode 100644 index 00000000000..3c4ad13016d --- /dev/null +++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.d/rsyslog @@ -0,0 +1,37 @@ +/var/log/syslog +{ + rotate 3 + daily + missingok + notifempty + delaycompress + compress + postrotate + invoke-rc.d rsyslog rotate > /dev/null + endscript +} + +/var/log/mail.info +/var/log/mail.warn +/var/log/mail.err +/var/log/mail.log +/var/log/daemon.log +/var/log/kern.log +/var/log/auth.log +/var/log/user.log +/var/log/lpr.log +/var/log/cron.log +/var/log/debug +/var/log/messages +{ + rotate 3 + daily + missingok + notifempty + compress + delaycompress + sharedscripts + postrotate + invoke-rc.d rsyslog rotate > /dev/null + endscript +} 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 9e65410f8fe..5b860c65043 100755 --- a/addons/point_of_sale/tools/posbox/posbox_create_image.sh +++ b/addons/point_of_sale/tools/posbox/posbox_create_image.sh @@ -35,6 +35,7 @@ git config core.sparsecheckout true echo "addons/web addons/web_kanban addons/hw_* +addons/point_of_sale/tools/posbox/configuration openerp/ odoo.py" | tee --append .git/info/sparse-checkout > /dev/null git read-tree -mu HEAD diff --git a/addons/product/product.py b/addons/product/product.py index 15566e403f3..69ed483d1a4 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -812,18 +812,24 @@ class product_template(osv.osv): if not name or any(term[0] == 'id' for term in (args or [])): return super(product_template, self).name_search( cr, user, name=name, args=args, operator=operator, context=context, limit=limit) - + template_ids = set() product_product = self.pool['product.product'] - results = product_product.name_search( - cr, user, name, args, operator=operator, context=context, limit=limit) + results = product_product.name_search(cr, user, name, args, operator=operator, context=context, limit=limit) product_ids = [p[0] for p in results] - template_ids = [p.product_tmpl_id.id - for p in product_product.browse( - cr, user, product_ids, context=context)] + for p in product_product.browse(cr, user, product_ids, context=context): + template_ids.add(p.product_tmpl_id.id) + while (results and len(template_ids) < limit): + domain = [('product_tmpl_id', 'not in', list(template_ids))] + results = product_product.name_search( + cr, user, name, args+domain, operator=operator, context=context, limit=limit) + product_ids = [p[0] for p in results] + for p in product_product.browse(cr, user, product_ids, context=context): + template_ids.add(p.product_tmpl_id.id) + # re-apply product.template order + name_get return super(product_template, self).name_search( - cr, user, '', args=[('id', 'in', template_ids)], + cr, user, '', args=[('id', 'in', list(template_ids))], operator='ilike', context=context, limit=limit) class product_product(osv.osv): diff --git a/addons/project/project.py b/addons/project/project.py index 5897ef2d1f3..a609c3257fd 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -905,7 +905,7 @@ class task(osv.osv): users_obj = self.pool.get('res.users') if context is None: context = {} - res = super(task, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu=submenu) + res = super(task, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu) # read uom as admin to avoid access rights issues, e.g. for portal/share users, # this should be safe (no context passed to avoid side-effects) diff --git a/addons/project/wizard/project_task_delegate.py b/addons/project/wizard/project_task_delegate.py index b56b6e425ff..d0c01b09b7a 100644 --- a/addons/project/wizard/project_task_delegate.py +++ b/addons/project/wizard/project_task_delegate.py @@ -91,7 +91,7 @@ class project_task_delegate(osv.osv_memory): } def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): - res = super(project_task_delegate, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu=submenu) + res = super(project_task_delegate, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu) users_pool = self.pool.get('res.users') obj_tm = users_pool.browse(cr, uid, uid, context=context).company_id.project_time_mode_id tm = obj_tm and obj_tm.name or 'Hours' diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml index ed55215ebeb..0e098efe4d8 100644 --- a/addons/purchase/edi/purchase_order_action_data.xml +++ b/addons/purchase/edi/purchase_order_action_data.xml @@ -37,7 +37,7 @@

  OUR REFERENCES
  RFQ number: ${object.name}
-   RFQ date: ${object.date_order}
+   RFQ date: ${format_tz(object.date_order, tz=user.tz, context={'lang':object.partner_id.lang})}
% if object.origin:   RFQ reference: ${object.origin}
% endif @@ -112,7 +112,7 @@   OUR REFERENCES
  Order number: ${object.name}
  Order total: ${object.amount_total} ${object.currency_id.name}
-   Order date: ${object.date_order}
+   Order date: ${format_tz(object.date_order, tz=user.tz, context={'lang':object.partner_id.lang})}
% if object.origin:   Order reference: ${object.origin}
% endif diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 3a977732467..24e11fb599d 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -38,6 +38,7 @@ class purchase_order(osv.osv): def _amount_all(self, cr, uid, ids, field_name, arg, context=None): res = {} cur_obj=self.pool.get('res.currency') + line_obj = self.pool['purchase.order.line'] for order in self.browse(cr, uid, ids, context=context): res[order.id] = { 'amount_untaxed': 0.0, @@ -47,10 +48,15 @@ class purchase_order(osv.osv): val = val1 = 0.0 cur = order.pricelist_id.currency_id for line in order.order_line: - taxes = self.pool.get('account.tax').compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id, order.partner_id) - val1 += cur_obj.round(cr, uid, cur, taxes['total']) #Decimal precision? - for c in taxes['taxes']: - val += c.get('amount', 0.0) + line_price = line_obj._calc_line_base_price(cr, uid, line, + context=context) + line_qty = line_obj._calc_line_quantity(cr, uid, line, + context=context) + taxes = self.pool['account.tax'].compute_all(cr, uid, line.taxes_id, line_price, line_qty, + line.product_id, order.partner_id) + val1 += cur_obj.round(cr, uid, cur, taxes['total']) #Decimal precision? + for c in taxes['taxes']: + val += c.get('amount', 0.0) res[order.id]['amount_tax']=cur_obj.round(cr, uid, cur, val) res[order.id]['amount_untaxed']=cur_obj.round(cr, uid, cur, val1) @@ -1014,12 +1020,34 @@ class purchase_order(osv.osv): class purchase_order_line(osv.osv): + def _calc_line_base_price(self, cr, uid, line, context=None): + """Return the base price of the line to be used for tax calculation. + + This function can be extended by other modules to modify this base + price (adding a discount, for example). + """ + return line.price_unit + + def _calc_line_quantity(self, cr, uid, line, context=None): + """Return the base quantity of the line to be used for the subtotal. + + This function can be extended by other modules to modify this base + quantity (adding for example offers 3x2 and so on). + """ + return line.product_qty + def _amount_line(self, cr, uid, ids, prop, arg, context=None): res = {} cur_obj=self.pool.get('res.currency') tax_obj = self.pool.get('account.tax') for line in self.browse(cr, uid, ids, context=context): - taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id, line.order_id.partner_id) + line_price = self._calc_line_base_price(cr, uid, line, + context=context) + line_qty = self._calc_line_quantity(cr, uid, line, + context=context) + taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line_price, + line_qty, line.product_id, + line.order_id.partner_id) cur = line.order_id.pricelist_id.currency_id res[line.id] = cur_obj.round(cr, uid, cur, taxes['total']) return res diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 8d4936293ca..906d8baba01 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -48,7 +48,12 @@ class sale_order(osv.osv): def _amount_line_tax(self, cr, uid, line, context=None): val = 0.0 - for c in self.pool.get('account.tax').compute_all(cr, uid, line.tax_id, line.price_unit * (1-(line.discount or 0.0)/100.0), line.product_uom_qty, line.product_id, line.order_id.partner_id)['taxes']: + line_obj = self.pool['sale.order.line'] + price = line_obj._calc_line_base_price(cr, uid, line, context=context) + qty = line_obj._calc_line_quantity(cr, uid, line, context=context) + for c in self.pool['account.tax'].compute_all( + cr, uid, line.tax_id, price, qty, line.product_id, + line.order_id.partner_id)['taxes']: val += c.get('amount', 0.0) return val @@ -827,6 +832,12 @@ class sale_order_line(osv.osv): return True return False + def _calc_line_base_price(self, cr, uid, line, context=None): + return line.price_unit * (1 - (line.discount or 0.0) / 100.0) + + def _calc_line_quantity(self, cr, uid, line, context=None): + return line.product_uom_qty + def _amount_line(self, cr, uid, ids, field_name, arg, context=None): tax_obj = self.pool.get('account.tax') cur_obj = self.pool.get('res.currency') @@ -834,8 +845,11 @@ class sale_order_line(osv.osv): if context is None: context = {} for line in self.browse(cr, uid, ids, context=context): - price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) - taxes = tax_obj.compute_all(cr, uid, line.tax_id, price, line.product_uom_qty, line.product_id, line.order_id.partner_id) + price = self._calc_line_base_price(cr, uid, line, context=context) + qty = self._calc_line_quantity(cr, uid, line, context=context) + taxes = tax_obj.compute_all(cr, uid, line.tax_id, price, qty, + line.product_id, + line.order_id.partner_id) cur = line.order_id.pricelist_id.currency_id res[line.id] = cur_obj.round(cr, uid, cur, taxes['total']) return res diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index c31595dc0c0..0d7df4688c8 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2967,6 +2967,10 @@ instance.web.form.FieldTextHtml = instance.web.form.AbstractField.extend(instanc } } }, + focus: function() { + var input = !this.get("effective_readonly") && this.$cleditor + return input ? input.focus() : false; + }, render_value: function() { if (! this.get("effective_readonly")) { this.$textarea.val(this.get('value') || ''); diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 150ca20a110..d424fd7b075 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -301,7 +301,7 @@ var $cell = $(cell); field.set_dimensions($cell.outerHeight(), $cell.outerWidth()); - field.$el.position({ + field.$el.css({top: 0, left: 0}).position({ my: 'left top', at: 'left top', of: $cell diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 66432568fd7..6beb648637c 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -1312,6 +1312,17 @@ instance.web_kanban.AbstractField = instance.web.Widget.extend(instance.web_kanb }, }); +instance.web_kanban.FormatChar = instance.web_kanban.AbstractField.extend({ + tagName: 'span', + init: function(parent, field, $node) { + this._super.apply(this, arguments); + this.format_descriptor = _.extend({}, this.field, {'widget': this.$node.attr('widget')}); + }, + renderElement: function() { + this.$el.text(instance.web.format_value(this.field.raw_value, this.format_descriptor)); + } +}); + instance.web_kanban.Priority = instance.web_kanban.AbstractField.extend({ init: function(parent, field, $node) { this._super.apply(this, arguments); @@ -1413,4 +1424,5 @@ instance.web_kanban.KanbanSelection = instance.web_kanban.AbstractField.extend({ instance.web_kanban.fields_registry = new instance.web.Registry({}); instance.web_kanban.fields_registry.add('priority','instance.web_kanban.Priority'); instance.web_kanban.fields_registry.add('kanban_state_selection','instance.web_kanban.KanbanSelection'); +instance.web_kanban.fields_registry.add('float_time','instance.web_kanban.FormatChar'); }; diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py index d10e33a4708..2d28cc449d8 100644 --- a/addons/website/models/ir_qweb.py +++ b/addons/website/models/ir_qweb.py @@ -344,7 +344,12 @@ class Image(orm.AbstractModel): if options.get('resize'): src = "%s/%s" % (src, options.get('resize')) - img = '' % (classes, src, options.get('style', '')) + alt = None + if options.get('alt-field') and getattr(record, options['alt-field'], None): + alt = record[options['alt-field']] + elif options.get('alt'): + alt = options['alt'] + img = '' % (classes, src, options.get('style', ''), ' alt="%s"' % alt if alt else '') return ir_qweb.HTMLSafe(img) local_url_re = re.compile(r'^/(?P[^]]+)/static/(?P.+)$') diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index b84be1a5440..75d03986438 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -8,7 +8,6 @@ from openerp import SUPERUSER_ID, api, tools from openerp.addons.website.models import website from openerp.http import request from openerp.osv import osv, fields -from openerp.tools import html_escape _logger = logging.getLogger(__name__) @@ -144,14 +143,6 @@ class view(osv.osv): # ensure there's only one match [root] = arch.xpath(section_xpath) - # html text need to be escaped for xml storage - def escape_node(node): - node.text = node.text and html_escape(node.text) - node.tail = node.tail and html_escape(node.tail) - escape_node(replacement) - for descendant in replacement.iterdescendants(): - escape_node(descendant) - root.text = replacement.text root.tail = replacement.tail # replace all children diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 16448fc01c1..985a5ac5237 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -1119,7 +1119,14 @@ define(['summernote/summernote'], function () { return this._save(); }, saveElement: function ($el) { - var markup = $el.prop('outerHTML'); + // escape text nodes for xml saving + var escaped_el = $el.clone(); + escaped_el.find('*').addBack().not('script,style').contents().each(function(){ + if(this.nodeType == 3) { + this.nodeValue = _.escape(this.nodeValue); + } + }); + var markup = escaped_el.prop('outerHTML'); return openerp.jsonRpc('/web/dataset/call', 'call', { model: 'ir.ui.view', method: 'save', diff --git a/addons/website/tests/test_views.py b/addons/website/tests/test_views.py index c8e7efb9044..b63ac75997d 100644 --- a/addons/website/tests/test_views.py +++ b/addons/website/tests/test_views.py @@ -175,14 +175,34 @@ class TestViewSaving(common.TransactionCase): ) def test_save_escaped_text(self): + """ Test saving html special chars in text nodes """ view_id = self.registry('ir.ui.view').create(self.cr, self.uid, { - 'arch':'hello world', + 'arch':'

hello world

', 'type':'qweb' }) view = self.registry('ir.ui.view').browse(self.cr, self.uid, view_id) - replacement = 'hello world & <angle brackets>!' - view.save(replacement, xpath='/t') - self.assertEqual(view.render(), replacement, 'html special characters wrongly escaped') + # script and style text nodes should not escaped client side + replacement = '' + view.save(replacement, xpath='/t/p/h1') + self.assertIn( + replacement.replace('&', '&'), + view.arch, + 'inline script should be escaped server side' + ) + self.assertIn( + replacement, + view.render(), + 'inline script should not be escaped when rendering' + ) + # common text nodes should be be escaped client side + replacement = 'world &amp; &lt;b&gt;cie' + view.save(replacement, xpath='/t/p') + self.assertIn(replacement, view.arch, 'common text node should not be escaped server side') + self.assertIn( + replacement, + view.render().replace('&', '&'), + 'text node characters wrongly unescaped when rendering' + ) def test_save_only_embedded(self): Company = self.registry('res.company') diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 4a8d6fa9500..6c122dd02ce 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -77,7 +77,7 @@
@@ -309,7 +309,7 @@
- +

Product Name