[MERGE] forward port of branch 8.0 up to d744923

This commit is contained in:
Christophe Simonis
2015-08-27 18:30:46 +02:00
39 changed files with 234 additions and 136 deletions
+1 -1
View File
@@ -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):
@@ -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");
});
+1 -1
View File
@@ -20,7 +20,7 @@
<p t-if="getLines(o)">
<t t-foreach="message(o, res_company)" t-as="messageline">
<span t-esc="messageline"/>
<p t-esc="messageline"/>
</t>
</p>
@@ -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
+1 -1
View File
@@ -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']")
@@ -40,7 +40,9 @@
<t t-call="web.login_layout">
<form class="oe_signup_form" role="form" method="post" t-if="not message">
<t t-call="auth_signup.fields"/>
<t t-call="auth_signup.fields">
<t t-set="only_passwords" t-value="bool(token)"/>
</t>
<p class="alert alert-danger" t-if="error">
<t t-esc="error"/>
+1 -1
View File
@@ -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
+3
View File
@@ -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):
+2 -2
View File
@@ -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()
+29 -67
View File
@@ -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.<br/><a href='/hw_proxy/status'>Ok</a>"
@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.<br/><a href="/hw_proxy/status">Ok</a>'
+1 -1
View File
@@ -1 +1 @@
__all__ = ["constants","escpos","exceptions","printer","supported_devices"]
__all__ = ["constants","escpos","exceptions","printer"]
@@ -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' },
]
+10 -1
View File
@@ -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 = """
<p>
To upgrade the posbox, click on the upgrade button. The upgrade will take a few minutes. <b>Do not reboot</b> the PosBox during the upgrade.
</p>
<p>
Latest patch:
</p>
<pre>
"""
upgrade_template += subprocess.check_output("git --work-tree=/home/pi/odoo/ --git-dir=/home/pi/odoo/.git log -1", shell=True).replace("\n", "<br/>")
upgrade_template += """
</pre>
<div class='centering'>
<a href='#' id='upgrade'>Upgrade</a>
</div>
@@ -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'
+2 -3
View File
@@ -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'
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
}
@@ -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
+13 -7
View File
@@ -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):
+1 -1
View File
@@ -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)
@@ -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'
@@ -37,7 +37,7 @@
<p style="border-left: 1px solid #8e0000; margin-left: 30px;">
&nbsp;&nbsp;<strong>OUR REFERENCES</strong><br />
&nbsp;&nbsp;RFQ number: <strong>${object.name}</strong><br />
&nbsp;&nbsp;RFQ date: ${object.date_order}<br />
&nbsp;&nbsp;RFQ date: ${format_tz(object.date_order, tz=user.tz, context={'lang':object.partner_id.lang})}<br />
% if object.origin:
&nbsp;&nbsp;RFQ reference: ${object.origin}<br />
% endif
@@ -112,7 +112,7 @@
&nbsp;&nbsp;<strong>OUR REFERENCES</strong><br />
&nbsp;&nbsp;Order number: <strong>${object.name}</strong><br />
&nbsp;&nbsp;Order total: <strong>${object.amount_total} ${object.currency_id.name}</strong><br />
&nbsp;&nbsp;Order date: ${object.date_order}<br />
&nbsp;&nbsp;Order date: ${format_tz(object.date_order, tz=user.tz, context={'lang':object.partner_id.lang})}<br />
% if object.origin:
&nbsp;&nbsp;Order reference: ${object.origin}<br />
% endif
+33 -5
View File
@@ -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
+17 -3
View File
@@ -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
+4
View File
@@ -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') || '');
@@ -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
+12
View File
@@ -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');
};
+6 -1
View File
@@ -344,7 +344,12 @@ class Image(orm.AbstractModel):
if options.get('resize'):
src = "%s/%s" % (src, options.get('resize'))
img = '<img class="%s" src="%s" style="%s"/>' % (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 = '<img class="%s" src="%s" style="%s"%s/>' % (classes, src, options.get('style', ''), ' alt="%s"' % alt if alt else '')
return ir_qweb.HTMLSafe(img)
local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
-9
View File
@@ -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
@@ -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',
+24 -4
View File
@@ -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':'<t>hello world</t>',
'arch':'<t><p><h1>hello world</h1></p></t>',
'type':'qweb'
})
view = self.registry('ir.ui.view').browse(self.cr, self.uid, view_id)
replacement = 'hello world &amp; &lt;angle brackets&gt;!'
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 = '<script>1 && "hello & world"</script>'
view.save(replacement, xpath='/t/p/h1')
self.assertIn(
replacement.replace('&', '&amp;'),
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;amp; &amp;lt;b&amp;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('&', '&amp;'),
'text node characters wrongly unescaped when rendering'
)
def test_save_only_embedded(self):
Company = self.registry('res.company')
+2 -2
View File
@@ -77,7 +77,7 @@
</div>
<div class="oe_product_image">
<a itemprop="url" t-att-href="keep('/shop/product/%s' % slug(product), page=(pager['page']['num'] if pager['page']['num']>1 else None))">
<span itemprop="image" t-field="product.image" t-field-options='{"widget": "image" #{None if product_image_big else ", \"resize\": \"300x300\""}}'/>
<span itemprop="image" t-field="product.image" t-field-options='{"widget": "image" #{None if product_image_big else ", \"resize\": \"300x300\""}}' t-att-alt="product.name"/>
</a>
</div>
<section>
@@ -309,7 +309,7 @@
</div>
<div class="row">
<div class="col-sm-7 col-md-7 col-lg-7">
<span itemprop="image" t-field="product.image" t-field-options='{"widget": "image", "class": "product_detail_img"}'/>
<span itemprop="image" t-field="product.image" t-field-options='{"widget": "image", "class": "product_detail_img", "alt-field": "name"}'/>
</div><div class="col-sm-5 col-md-5 col-lg-4 col-lg-offset-1">
<h1 itemprop="name" t-field="product.name">Product Name</h1>
<span itemprop="url" style="display:none;" t-esc="'/shop/product/%s' % slug(product)"/>
+3 -2
View File
@@ -29,6 +29,7 @@ from openerp.osv import fields, osv
from openerp.tools import float_round, float_is_zero, float_compare
from openerp.tools.translate import _
from openerp.exceptions import UserError
import simplejson as json
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
@@ -292,9 +293,9 @@ class res_currency(osv.osv):
format_number_str = "openerp.web.format_value(arguments[0], {type: 'float', digits: [69," + str(digits) + "]}, 0.00)"
if row['position'] == 'after':
return_str = "return " + format_number_str + " + '\\xA0" + symbol + "';"
return_str = "return " + format_number_str + " + '\\xA0' + " + json.dumps(symbol) + ";"
else:
return_str = "return '" + symbol + "\\xA0' + " + format_number_str + ";"
return_str = "return " + json.dumps(symbol) + " + '\\xA0' + " + format_number_str + ";"
function += "if (arguments[1] === " + str(row['id']) + ") { " + return_str + " }"
return function