[MERGE] forward port of branch saas-6 up to 7a768a4
Due to `sale` rewrite (94716a3f14), the commit503820acb6has been partially ignored (in sale.order.line) and will be rewritten later using new-api.
This commit is contained in:
@@ -661,6 +661,14 @@ class AccountTax(models.Model):
|
||||
recs = self.browse(cr, uid, ids, context=context)
|
||||
return recs.compute_all(price_unit, currency, quantity, product, partner)
|
||||
|
||||
@api.model
|
||||
def _fix_tax_included_price(self, price, prod_taxes, line_taxes):
|
||||
"""Subtract tax amount from price when corresponding "price included" taxes do not apply"""
|
||||
# FIXME get currency in param?
|
||||
incl_tax = prod_taxes.filtered(lambda tax: tax.id not in line_taxes and tax.price_include)
|
||||
if incl_tax:
|
||||
return incl_tax.compute_all(price)['total_excluded']
|
||||
return price
|
||||
|
||||
class AccountOperationTemplate(models.Model):
|
||||
_name = "account.operation.template"
|
||||
|
||||
@@ -559,7 +559,7 @@ class AccountInvoice(models.Model):
|
||||
if self.currency_id != company_currency:
|
||||
currency = self.currency_id.with_context(date=self.date_invoice or fields.Date.context_today(self))
|
||||
line['currency_id'] = currency.id
|
||||
line['amount_currency'] = line['price']
|
||||
line['amount_currency'] = currency.round(line['price'])
|
||||
line['price'] = currency.compute(line['price'], company_currency)
|
||||
else:
|
||||
line['currency_id'] = False
|
||||
|
||||
@@ -1832,9 +1832,11 @@ var bankStatementReconciliationLine = abstractReconciliationLine.extend({
|
||||
|
||||
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.$dropdown.trigger("click");
|
||||
});
|
||||
},
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
<field name="tax_ids" widget="one2many_list" nolabel="1">
|
||||
<tree string="Tax Mapping" editable="bottom">
|
||||
<field name="tax_src_id" domain="[('type_tax_use', '!=', None)]"/>
|
||||
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None)]"/>
|
||||
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None), ('price_include', '=', False)]"/>
|
||||
</tree>
|
||||
<form string="Tax Mapping">
|
||||
<field name="tax_src_id" domain="[('type_tax_use', '!=', None)]"/>
|
||||
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None)]"/>
|
||||
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None), ('price_include', '=', False)]"/>
|
||||
</form>
|
||||
</field>
|
||||
</group>
|
||||
|
||||
@@ -39,7 +39,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"/>
|
||||
|
||||
@@ -363,6 +363,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):
|
||||
|
||||
@@ -62,7 +62,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()
|
||||
@@ -82,7 +82,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()
|
||||
|
||||
@@ -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 @@
|
||||
__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' },
|
||||
]
|
||||
@@ -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'
|
||||
|
||||
@@ -521,7 +521,7 @@ class pos_session(osv.osv):
|
||||
}) for journal in pos_config.journal_ids]
|
||||
|
||||
values.update({
|
||||
'name': self.pool['ir.sequence'].next_by_code(cr, uid, 'pos.session'),
|
||||
'name': self.pool['ir.sequence'].next_by_code(cr, uid, 'pos.session', context=context),
|
||||
'statement_ids': statements,
|
||||
'config_id': config_id
|
||||
})
|
||||
|
||||
@@ -29,7 +29,7 @@ class pos_details(report_sxw.rml_parse):
|
||||
user_ids = form['user_ids'] or self._get_all_users()
|
||||
company_id = user_obj.browse(self.cr, self.uid, self.uid).company_id.id
|
||||
pos_ids = pos_obj.search(self.cr, self.uid, [('date_order','>=',form['date_start'] + ' 00:00:00'),('date_order','<=',form['date_end'] + ' 23:59:59'),('user_id','in',user_ids),('state','in',['done','paid','invoiced']),('company_id','=',company_id)])
|
||||
for pos in pos_obj.browse(self.cr, self.uid, pos_ids):
|
||||
for pos in pos_obj.browse(self.cr, self.uid, pos_ids, context=self.localcontext):
|
||||
for pol in pos.lines:
|
||||
result = {
|
||||
'code': pol.product_id.default_code,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -772,18 +772,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):
|
||||
|
||||
@@ -18,6 +18,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,
|
||||
@@ -27,8 +28,14 @@ class purchase_order(osv.osv):
|
||||
val = val1 = 0.0
|
||||
cur = order.pricelist_id.currency_id
|
||||
for line in order.order_line:
|
||||
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 = line.taxes_id.compute_all(line_price, cur, line_qty,
|
||||
line.product_id, order.partner_id)
|
||||
val1 += line.price_subtotal
|
||||
for c in line.taxes_id.compute_all(line.price_unit, cur, line.product_qty, product=line.product_id, partner=order.partner_id)['taxes']:
|
||||
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)
|
||||
@@ -1020,11 +1027,35 @@ 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 = {}
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
line_price = self._calc_line_base_price(cr, uid, line,
|
||||
context=context)
|
||||
line_qty = self._calc_line_quantity(cr, uid, line,
|
||||
context=context)
|
||||
cur = line.order_id.pricelist_id.currency_id
|
||||
res[line.id] = line.taxes_id.compute_all(line.price_unit, cur, line.product_qty, product=line.product_id, partner=line.order_id.partner_id)['total_excluded']
|
||||
taxes = line.taxes_id.compute_all(line_price, cur,
|
||||
line_qty, line.product_id,
|
||||
line.order_id.partner_id)
|
||||
|
||||
res[line.id] = taxes['total_excluded']
|
||||
return res
|
||||
|
||||
def _get_uom_id(self, cr, uid, context=None):
|
||||
@@ -1220,6 +1251,7 @@ class purchase_order_line(osv.osv):
|
||||
taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
|
||||
fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
|
||||
taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
|
||||
price = self.pool['account.tax']._fix_tax_included_price(cr, uid, price, product.supplier_taxes_id, taxes_ids)
|
||||
res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
|
||||
|
||||
return res
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_purchase_to_invoice
|
||||
from . import test_onchange_product_id
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
from openerp.tests.common import TransactionCase
|
||||
|
||||
class TestOnchangeProductId(TransactionCase):
|
||||
"""Test that when an included tax is mapped by a fiscal position, the included tax must be
|
||||
subtracted to the price of the product.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestOnchangeProductId, self).setUp()
|
||||
self.fiscal_position_model = self.registry('account.fiscal.position')
|
||||
self.fiscal_position_tax_model = self.registry('account.fiscal.position.tax')
|
||||
self.tax_model = self.registry('account.tax')
|
||||
self.pricelist_model = self.registry('product.pricelist')
|
||||
self.res_partner_model = self.registry('res.partner')
|
||||
self.product_model = self.registry('product.product')
|
||||
self.product_uom_model = self.registry('product.uom')
|
||||
self.so_line_model = self.registry('purchase.order.line')
|
||||
|
||||
def test_onchange_product_id(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
uom_id = self.product_uom_model.search(cr, uid, [('name', '=', 'Unit(s)')])[0]
|
||||
pricelist = self.pricelist_model.search(cr, uid, [('name', '=', 'Public Pricelist')])[0]
|
||||
partner_id = self.res_partner_model.create(cr, uid, dict(name="George"))
|
||||
tax_include_id = self.tax_model.create(cr, uid, dict(name="Include tax",
|
||||
amount_type='percent',
|
||||
amount='21.0',
|
||||
type_tax_use='purchase',
|
||||
price_include=True))
|
||||
tax_exclude_id = self.tax_model.create(cr, uid, dict(name="Exclude tax",
|
||||
amount_type='percent',
|
||||
type_tax_use='purchase',
|
||||
amount='0.00'))
|
||||
product_id = self.product_model.create(cr, uid, dict(name="Voiture",
|
||||
list_price='121',
|
||||
supplier_taxes_id=[(6, 0, [tax_include_id])]))
|
||||
fp_id = self.fiscal_position_model.create(cr, uid, dict(name="fiscal position",
|
||||
sequence=1))
|
||||
self.fiscal_position_tax_model.create(cr, uid, dict(position_id=fp_id,
|
||||
tax_src_id=tax_include_id,
|
||||
tax_dest_id=tax_exclude_id))
|
||||
# import pudb; pudb.set_trace()
|
||||
res = self.so_line_model.onchange_product_id(cr, uid, [], pricelist, product_id, 1.0, uom_id, partner_id,
|
||||
fiscal_position_id=fp_id)
|
||||
|
||||
self.assertEquals(100, res['value']['price_unit'], "The included tax must be subtracted to the price")
|
||||
@@ -224,7 +224,8 @@ class Report(osv.Model):
|
||||
# Run wkhtmltopdf process
|
||||
return self._run_wkhtmltopdf(
|
||||
cr, uid, headerhtml, footerhtml, contenthtml, context.get('landscape'),
|
||||
paperformat, specific_paperformat_args, save_in_attachment
|
||||
paperformat, specific_paperformat_args, save_in_attachment,
|
||||
context.get('set_viewport_size')
|
||||
)
|
||||
|
||||
@api.v8
|
||||
@@ -316,7 +317,7 @@ class Report(osv.Model):
|
||||
def _check_wkhtmltopdf(self):
|
||||
return wkhtmltopdf_state
|
||||
|
||||
def _run_wkhtmltopdf(self, cr, uid, headers, footers, bodies, landscape, paperformat, spec_paperformat_args=None, save_in_attachment={}):
|
||||
def _run_wkhtmltopdf(self, cr, uid, headers, footers, bodies, landscape, paperformat, spec_paperformat_args=None, save_in_attachment=None, set_viewport_size=False):
|
||||
"""Execute wkhtmltopdf as a subprocess in order to convert html given in input into a pdf
|
||||
document.
|
||||
|
||||
@@ -330,7 +331,7 @@ class Report(osv.Model):
|
||||
:returns: Content of the pdf as a string
|
||||
"""
|
||||
command_args = []
|
||||
if wkhtmltopdf_state == "ok":
|
||||
if set_viewport_size:
|
||||
command_args.extend(['--viewport-size', landscape and '1024x1280' or '1280x1024'])
|
||||
|
||||
# Passing the cookie to wkhtmltopdf in order to resolve internal links.
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id}"
|
||||
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"
|
||||
/>
|
||||
<field name="invoice_status" invisible="1"/>
|
||||
@@ -193,7 +193,7 @@
|
||||
<label for="product_uom_qty" string="Ordered Quantity"/>
|
||||
<div>
|
||||
<field
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'uom_qty_change':True}"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'uom_qty_change':True, 'company_id': parent.company_id}"
|
||||
name="product_uom_qty" class="oe_inline"/>
|
||||
<field name="product_uom" groups="product.group_uom" class="oe_inline oe_no_button"
|
||||
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"/>
|
||||
@@ -234,12 +234,13 @@
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="product_id"
|
||||
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id}"
|
||||
/>
|
||||
<field name="name"/>
|
||||
<field name="product_uom_qty"
|
||||
string="Ordered Qty"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}"/>
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id}"
|
||||
/>
|
||||
<field name="qty_delivered" invisible="not context.get('show_sale')"
|
||||
attrs="{'readonly': [('qty_delivered_updateable', '=', False)]}"/>
|
||||
<field name="qty_invoiced"
|
||||
@@ -247,6 +248,7 @@
|
||||
<field name="qty_to_invoice" invisible="1"/>
|
||||
<field name="product_uom"
|
||||
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"
|
||||
context="{'company_id': parent.company_id}"
|
||||
groups="product.group_uom" options='{"no_open": True}'/>
|
||||
<field name="price_unit"
|
||||
attrs="{'readonly': [('qty_invoiced', '>', 0)]}"/>
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import test_sale_to_invoice
|
||||
import test_sale_order
|
||||
from . import test_product_id_change
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import unittest2
|
||||
from openerp.tests.common import TransactionCase
|
||||
|
||||
class TestProductIdChange(TransactionCase):
|
||||
"""Test that when an included tax is mapped by a fiscal position, the included tax must be
|
||||
subtracted to the price of the product.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestProductIdChange, self).setUp()
|
||||
self.fiscal_position_model = self.registry('account.fiscal.position')
|
||||
self.fiscal_position_tax_model = self.registry('account.fiscal.position.tax')
|
||||
self.tax_model = self.registry('account.tax')
|
||||
self.pricelist_model = self.registry('product.pricelist')
|
||||
self.res_partner_model = self.registry('res.partner')
|
||||
self.product_tmpl_model = self.registry('product.template')
|
||||
self.product_model = self.registry('product.product')
|
||||
self.so_line_model = self.registry('sale.order.line')
|
||||
|
||||
@unittest2.skip("""
|
||||
Due to `sale` rewrite (94716a3f14d929574c46ef165e4364d12a5903ed),
|
||||
the commit 503820acb6d82a85c0c49ac26f7e7f8cd73851dd has been partially ignored
|
||||
during the forward-port to master and need to be rewritten using new-api.
|
||||
""")
|
||||
def test_product_id_change(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
pricelist = self.pricelist_model.search(cr, uid, [('name', '=', 'Public Pricelist')])[0]
|
||||
partner_id = self.res_partner_model.create(cr, uid, dict(name="George"))
|
||||
tax_include_id = self.tax_model.create(cr, uid, dict(name="Include tax",
|
||||
type='percent',
|
||||
amount='0.21',
|
||||
price_include=True))
|
||||
tax_exclude_id = self.tax_model.create(cr, uid, dict(name="Exclude tax",
|
||||
type='percent',
|
||||
amount='0.00'))
|
||||
product_tmpl_id = self.product_tmpl_model.create(cr, uid, dict(name="Voiture",
|
||||
list_price='121',
|
||||
taxes_id=[(6, 0, [tax_include_id])]))
|
||||
product_id = self.product_model.create(cr, uid, dict(product_tmpl_id=product_tmpl_id))
|
||||
fp_id = self.fiscal_position_model.create(cr, uid, dict(name="fiscal position",
|
||||
sequence=1))
|
||||
fp_tax_id = self.fiscal_position_tax_model.create(cr, uid, dict(position_id=fp_id,
|
||||
tax_src_id=tax_include_id,
|
||||
tax_dest_id=tax_exclude_id))
|
||||
res = self.so_line_model.product_id_change(cr, uid, [], pricelist, product_id, partner_id=partner_id,
|
||||
fiscal_position=fp_id)
|
||||
self.assertEquals(100, res['value']['price_unit'], "The included tax must be subtracted to the price")
|
||||
@@ -27,7 +27,7 @@
|
||||
</xpath>
|
||||
<xpath expr="//page/field[@name='order_line']/form/group/group/field[@name='tax_id']" position="before">
|
||||
<field name="product_tmpl_id" invisible="1"/>
|
||||
<field name="product_packaging" context="{'default_product_tmpl_id': product_tmpl_id, 'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}" domain="[('product_tmpl_id','=',product_tmpl_id)]" groups="product.group_stock_packaging" />
|
||||
<field name="product_packaging" context="{'default_product_tmpl_id': product_tmpl_id, 'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id}" domain="[('product_tmpl_id','=',product_tmpl_id)]" groups="product.group_stock_packaging" />
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='order_line']/form/group/group/field[@name='price_unit']" position="before">
|
||||
<field name="route_id" groups="sale_stock.group_route_so_lines"/>
|
||||
|
||||
@@ -845,7 +845,7 @@ class survey_user_input(osv.Model):
|
||||
'survey_id': fields.many2one('survey.survey', 'Survey', required=True,
|
||||
readonly=1, ondelete='restrict'),
|
||||
'date_create': fields.datetime('Creation Date', required=True,
|
||||
readonly=1),
|
||||
readonly=1, copy=False),
|
||||
'deadline': fields.datetime("Deadline",
|
||||
help="Date by which the person can open the survey and submit answers",
|
||||
oldname="date_deadline"),
|
||||
@@ -858,7 +858,7 @@ class survey_user_input(osv.Model):
|
||||
'Status',
|
||||
readonly=True),
|
||||
'test_entry': fields.boolean('Test entry', readonly=1),
|
||||
'token': fields.char("Identification token", readonly=1, required=1),
|
||||
'token': fields.char("Identification token", readonly=1, required=1, copy=False),
|
||||
|
||||
# Optional Identification data
|
||||
'partner_id': fields.many2one('res.partner', 'Partner', readonly=1),
|
||||
@@ -869,7 +869,7 @@ class survey_user_input(osv.Model):
|
||||
'Last displayed page'),
|
||||
# The answers !
|
||||
'user_input_line_ids': fields.one2many('survey.user_input_line',
|
||||
'user_input_id', 'Answers'),
|
||||
'user_input_id', 'Answers', copy=True),
|
||||
|
||||
# URLs used to display the answers
|
||||
'result_url': fields.related('survey_id', 'result_url', type='char',
|
||||
@@ -892,9 +892,6 @@ class survey_user_input(osv.Model):
|
||||
('deadline_in_the_past', 'CHECK (deadline >= date_create)', 'The deadline cannot be in the past')
|
||||
]
|
||||
|
||||
def copy_data(self, cr, uid, id, default=None, context=None):
|
||||
raise UserError(_('You cannot duplicate this element!'))
|
||||
|
||||
def do_clean_emptys(self, cr, uid, automatic=False, context=None):
|
||||
''' Remove empty user inputs that have been created manually
|
||||
(used as a cronjob declared in data/survey_cron.xml) '''
|
||||
@@ -1023,9 +1020,6 @@ class survey_user_input_line(osv.Model):
|
||||
vals.update({'quizz_mark': self.__get_mark(cr, uid, value_suggested)})
|
||||
return super(survey_user_input_line, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def copy_data(self, cr, uid, id, default=None, context=None):
|
||||
raise UserError(_('You cannot duplicate this element!'))
|
||||
|
||||
def save_lines(self, cr, uid, user_input_id, question, post, answer_tag,
|
||||
context=None):
|
||||
''' Save answers to questions, depending on question type
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -454,7 +454,7 @@ ListView.include(/** @lends instance.web.ListView# */{
|
||||
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
|
||||
|
||||
@@ -341,7 +341,14 @@ class Image(orm.AbstractModel):
|
||||
max_size = '' if max_size is None else '/%s' % max_size
|
||||
src = '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field_name, max_size, sha)
|
||||
|
||||
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>.+)$')
|
||||
|
||||
@@ -90,6 +90,10 @@ var FieldTextHtmlSimple = widget.extend({
|
||||
}
|
||||
return value;
|
||||
},
|
||||
focus: function() {
|
||||
var input = !this.get("effective_readonly") && this.$textarea;
|
||||
return input ? input.focus() : false;
|
||||
},
|
||||
render_value: function() {
|
||||
var value = this.get('value');
|
||||
this.$textarea.val(value || '');
|
||||
|
||||
@@ -426,13 +426,21 @@ var RTE = Widget.extend({
|
||||
if (this.__saved[key]) return true;
|
||||
this.__saved[key] = true;
|
||||
}
|
||||
// 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 ajax.jsonRpc('/web/dataset/call', 'call', {
|
||||
model: 'ir.ui.view',
|
||||
method: 'save',
|
||||
args: [
|
||||
$el.data('oe-id'),
|
||||
$el.prop('outerHTML'),
|
||||
markup,
|
||||
$el.data('oe-xpath') || null,
|
||||
context || base.get_context()
|
||||
],
|
||||
|
||||
@@ -24,6 +24,7 @@ var options = $.summernote.options;
|
||||
|
||||
dom.hasContentAfter = function (node) {
|
||||
var next;
|
||||
if(dom.isEditable(node)) return;
|
||||
while (node.nextSibling) {
|
||||
next = node.nextSibling;
|
||||
if (next.tagName || dom.isVisibleText(next) || dom.isBR(next)) return next;
|
||||
@@ -32,6 +33,7 @@ dom.hasContentAfter = function (node) {
|
||||
};
|
||||
dom.hasContentBefore = function (node) {
|
||||
var prev;
|
||||
if(dom.isEditable(node)) return;
|
||||
while (node.previousSibling) {
|
||||
prev = node.previousSibling;
|
||||
if (prev.tagName || dom.isVisibleText(prev) || dom.isBR(prev)) return prev;
|
||||
@@ -40,12 +42,12 @@ dom.hasContentBefore = function (node) {
|
||||
};
|
||||
dom.ancestorHaveNextSibling = function (node, pred) {
|
||||
pred = pred || dom.hasContentAfter;
|
||||
while (!node.nextSibling || !pred(node)) { node = node.parentNode; }
|
||||
while (!dom.isEditable(node) && (!node.nextSibling || !pred(node))) { node = node.parentNode; }
|
||||
return node;
|
||||
};
|
||||
dom.ancestorHavePreviousSibling = function (node, pred) {
|
||||
pred = pred || dom.hasContentBefore;
|
||||
while (!node.previousSibling || !pred(node)) { node = node.parentNode; }
|
||||
while (!dom.isEditable(node) && (!node.previousSibling || !pred(node))) { node = node.parentNode; }
|
||||
return node;
|
||||
};
|
||||
dom.nextElementSibling = function (node) {
|
||||
@@ -369,6 +371,7 @@ dom.removeSpace = function (node, begin, so, end, eo) {
|
||||
|
||||
(function __remove_space (node) {
|
||||
if (!node) return;
|
||||
var t_begin, t_end;
|
||||
for (var k=0; k<node.childNodes.length; k++) {
|
||||
var cur = node.childNodes[k];
|
||||
|
||||
@@ -385,14 +388,20 @@ dom.removeSpace = function (node, begin, so, end, eo) {
|
||||
if (node.childNodes.length > 1 && dom.isText(cur) && !dom.isVisibleText(cur)) {
|
||||
removed = true;
|
||||
if (cur === begin) {
|
||||
so = 0;
|
||||
begin = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(cur)));
|
||||
t_begin = dom.hasContentBefore(dom.ancestorHavePreviousSibling(cur));
|
||||
if(t_begin) {
|
||||
so = 0;
|
||||
begin = dom.lastChild(t_begin);
|
||||
}
|
||||
}
|
||||
if (cur === end) {
|
||||
eo = 1;
|
||||
end = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(cur)));
|
||||
if (dom.isText(end)) {
|
||||
eo = end.textContent.length;
|
||||
t_end = dom.hasContentAfter(dom.ancestorHaveNextSibling(cur));
|
||||
if(t_end) {
|
||||
eo = 1;
|
||||
end = dom.firstChild(t_end);
|
||||
if (dom.isText(end)) {
|
||||
eo = end.textContent.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
cur.parentNode.removeChild(cur);
|
||||
@@ -429,7 +438,7 @@ dom.removeSpace = function (node, begin, so, end, eo) {
|
||||
sc: begin,
|
||||
ec: end,
|
||||
so: !dom.isBR(begin) && so > 0 ? so : 0,
|
||||
eo: eo
|
||||
eo: dom.isBR(end) ? 0 : eo
|
||||
};
|
||||
};
|
||||
dom.node = function (node) {
|
||||
@@ -448,47 +457,61 @@ dom.pasteText = function (textNode, offset, text, isOnlyText) {
|
||||
data.sc = node.insertBefore(document.createTextNode(" "), data.sc);
|
||||
} else if (node.firstChild && !dom.isBR(dom.firstChild(node))) {
|
||||
data.sc = node.insertBefore(document.createTextNode(" "), dom.firstChild(node));
|
||||
} else if (dom.isBR(node)) {
|
||||
data.sc = node.parentNode.insertBefore(document.createTextNode(" "), node);
|
||||
} else {
|
||||
data.sc = node.appendChild(document.createTextNode(" "));
|
||||
}
|
||||
data.so = 0;
|
||||
}
|
||||
data.sc.splitText(data.so);
|
||||
var first = data.sc;
|
||||
var last = data.sc.nextSibling;
|
||||
var try_to_clean = [first, first.splitText(data.so)];
|
||||
|
||||
isOnlyText = isOnlyText || !text.match('\n');
|
||||
|
||||
|
||||
var sc, so, ec, eo;
|
||||
|
||||
if (!isOnlyText) {
|
||||
var paste_after = dom.isBR(node) ? node.parentElement : node;
|
||||
|
||||
// tag to close and open
|
||||
var tag = node.tagName.toLowerCase();
|
||||
var tag = paste_after.tagName.toLowerCase();
|
||||
if(dom.pasteTextApply.indexOf(tag) === -1) {
|
||||
text = text.split('\n').join("<br/>");
|
||||
paste_after = data.sc;
|
||||
} else {
|
||||
text = "<"+tag+">"+text.split('\n').join("</"+tag+"><"+tag+">")+"</"+tag+">";
|
||||
try_to_clean.push(paste_after);
|
||||
}
|
||||
|
||||
var $text = $(text);
|
||||
var nodes = _.toArray($('<div/>').html(text).prop('childNodes'));
|
||||
sc = nodes[0];
|
||||
so = 0;
|
||||
ec = dom.lastChild(nodes[nodes.length - 1]);
|
||||
eo = dom.nodeLength(ec);
|
||||
|
||||
// split parent node and insert text
|
||||
if(dom.pasteTextClose.indexOf(tag) !== -1) {
|
||||
var $next = $(node).clone().empty();
|
||||
$next.append( last );
|
||||
$(node).after( $next );
|
||||
$(node).after( $text );
|
||||
if(!dom.isText(paste_after) && dom.pasteTextClose.indexOf(tag) !== -1) {
|
||||
try_to_clean.push(dom.splitTree(paste_after, {node: first, offset: data.so}));
|
||||
$(paste_after).after( nodes );
|
||||
} else {
|
||||
$(data.sc).after( $text );
|
||||
$(paste_after).after( nodes );
|
||||
}
|
||||
} else {
|
||||
sc = ec = first;
|
||||
so = dom.nodeLength(first);
|
||||
eo = so + text.length;
|
||||
first.appendData( text );
|
||||
}
|
||||
|
||||
// clean the dom content
|
||||
data = dom.merge(node.parentNode.parentNode, last, 0, last, 0, null, true);
|
||||
data = dom.removeSpace(node.parentNode.parentNode, data.sc, data.so, data.ec, data.eo);
|
||||
|
||||
// move caret
|
||||
range.create(data.sc, data.so, data.ec, data.eo).select();
|
||||
range.create(sc, so, ec, eo).clean().select();
|
||||
|
||||
_(try_to_clean).each(function(node){
|
||||
_(dom.listDescendant(node).reverse().concat(node)).each(function(subnode){
|
||||
if(dom.isEmpty(subnode) || (dom.isText(subnode) && !dom.isVisibleText(subnode))) $(subnode).remove();
|
||||
});
|
||||
});
|
||||
};
|
||||
dom.removeBetween = function (sc, so, ec, eo, towrite) {
|
||||
if (ec.tagName) {
|
||||
@@ -508,7 +531,7 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) {
|
||||
}
|
||||
}
|
||||
if (!eo && sc !== ec) {
|
||||
ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(ec)));
|
||||
ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(ec)) || ec);
|
||||
eo = ec.textContent.length;
|
||||
}
|
||||
|
||||
@@ -555,8 +578,13 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) {
|
||||
}
|
||||
}
|
||||
|
||||
sc = before ? dom.lastChild(before) : dom.firstChild(after);
|
||||
so = sc.textContent.length;
|
||||
if (dom.listAncestor(after).length <= dom.listAncestor(before).length) {
|
||||
sc = dom.lastChild(before || ancestor);
|
||||
so = dom.nodeLength(sc);
|
||||
} else {
|
||||
sc = dom.firstChild(after);
|
||||
so = 0;
|
||||
}
|
||||
|
||||
if (before) {
|
||||
var text = sc.textContent.replace(/[ \t\n\r]+$/, '\u00A0');
|
||||
@@ -565,7 +593,7 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) {
|
||||
}
|
||||
if (towrite && !node.firstChild && node.parentNode && !dom.isNotBreakable(node)) {
|
||||
var br = $("<br/>")[0];
|
||||
node.appendChild(br);
|
||||
node.appendChild(sc);
|
||||
sc = br;
|
||||
so = 0;
|
||||
}
|
||||
@@ -829,13 +857,13 @@ range.WrappedRange.prototype.reRange = function (keep_end, isNotBreakable) {
|
||||
if ($.contains(start, end)) {
|
||||
|
||||
if (keep_end) {
|
||||
sc = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)));
|
||||
sc = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)) || sc);
|
||||
so = sc.textContent.length;
|
||||
} else if (!eo) {
|
||||
ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)));
|
||||
ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)) || ec);
|
||||
eo = ec.textContent.length;
|
||||
} else {
|
||||
ec = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(end)));
|
||||
ec = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(end)) || ec);
|
||||
eo = 0;
|
||||
}
|
||||
} else {
|
||||
@@ -864,6 +892,7 @@ range.WrappedRange.prototype.deleteContents = function (towrite) {
|
||||
};
|
||||
range.WrappedRange.prototype.clean = function (mergeFilter, all) {
|
||||
var node = dom.node(this.sc === this.ec ? this.sc : this.commonAncestor());
|
||||
node = node || $(this.sc).closest('[contenteditable]')[0];
|
||||
if (node.childNodes.length <=1) {
|
||||
return this;
|
||||
}
|
||||
@@ -941,7 +970,7 @@ $.summernote.pluginEvents.tab = function (event, editor, layoutInfo, outdent) {
|
||||
var r = range.create();
|
||||
var outdent = outdent || false;
|
||||
|
||||
if (r.isCollapsed()) {
|
||||
if (r && r.isCollapsed()) {
|
||||
if (r.isOnCell() && r.isOnCellFirst()) {
|
||||
var td = dom.ancestor(r.sc, dom.isCell);
|
||||
if (!outdent && !dom.nextElementSibling(td) && !dom.nextElementSibling(td.parentNode)) {
|
||||
@@ -1104,7 +1133,9 @@ $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
|
||||
$editable.data('NoteHistory').recordUndo($editable, "visible");
|
||||
|
||||
var r = range.create();
|
||||
if (r && !r.isCollapsed()) {
|
||||
if (!r) return;
|
||||
|
||||
if (!r.isCollapsed()) {
|
||||
r = r.deleteContents(true);
|
||||
r.select();
|
||||
}
|
||||
@@ -1208,7 +1239,7 @@ $.summernote.pluginEvents.delete = function (event, editor, layoutInfo) {
|
||||
}
|
||||
while (dom.isImg(node)) {
|
||||
parent = node.parentNode;
|
||||
index = dom.makeOffsetPath(parent, node)[0];
|
||||
index = dom.position(node);
|
||||
if (index>0) {
|
||||
var next = node.previousSibling;
|
||||
range.create(next,next.textContent.length).select();
|
||||
@@ -1289,6 +1320,7 @@ $.summernote.pluginEvents.backspace = function (event, editor, layoutInfo) {
|
||||
$editable.data('NoteHistory').recordUndo($editable, "backspace");
|
||||
|
||||
var r = range.create();
|
||||
if (!r) return;
|
||||
if (!r.isContentEditable()) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
@@ -1313,10 +1345,15 @@ $.summernote.pluginEvents.backspace = function (event, editor, layoutInfo) {
|
||||
var node = dom.node(target);
|
||||
var data = dom.merge(node, target, offset, target, offset, null, true);
|
||||
data = dom.removeSpace(node.parentNode, data.sc, data.so, data.ec, data.eo);
|
||||
r = range.create(data.sc, data.so);
|
||||
r = dom.isVoid(data.sc) ? range.createFromNode(data.sc) : range.create(data.sc, data.so);
|
||||
r.select();
|
||||
target = r.sc;
|
||||
offset = r.so;
|
||||
if (target.tagName && target.childNodes[offset]) {
|
||||
target = target.childNodes[offset];
|
||||
offset = 0;
|
||||
node = dom.node(target);
|
||||
}
|
||||
|
||||
while (node.parentNode && !dom.hasContentAfter(node) && !dom.hasContentBefore(node) && !dom.isImg(node)) {node = node.parentNode;}
|
||||
|
||||
@@ -1779,6 +1816,22 @@ eventHandler.toolbar.button.updateRecentColor = function (elBtn, sEvent, sValue)
|
||||
return false;
|
||||
};
|
||||
|
||||
$(document).on('click keyup', function () {
|
||||
var $popover = $((range.create()||{}).sc).closest('[contenteditable]');
|
||||
var popover_history = ($popover.data()||{}).NoteHistory;
|
||||
if(!popover_history || popover_history == history) return;
|
||||
var editor = $popover.parent('.note-editor');
|
||||
$('button[data-event="undo"]', editor).attr('disabled', !popover_history.hasUndo());
|
||||
$('button[data-event="redo"]', editor).attr('disabled', !popover_history.hasRedo());
|
||||
});
|
||||
|
||||
eventHandler.editor.undo = function ($popover) {
|
||||
if(!$popover.attr('disabled')) $popover.data('NoteHistory').undo();
|
||||
};
|
||||
eventHandler.editor.redo = function ($popover) {
|
||||
if(!$popover.attr('disabled')) $popover.data('NoteHistory').redo();
|
||||
};
|
||||
|
||||
options.fontSizes = [_t('Default'), 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 62];
|
||||
$.summernote.pluginEvents.applyFont = function (event, editor, layoutInfo, color, bgcolor, size) {
|
||||
var rng = range.create();
|
||||
@@ -2183,9 +2236,11 @@ function summernote_paste (event) {
|
||||
if (!r.isCollapsed()) {
|
||||
r = r.deleteContents();
|
||||
r.select();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
var text = clipboardData.getData("text/plain").replace(/</g, "<");
|
||||
var text = $('<div />').text(clipboardData.getData("text/plain").toString()).html();
|
||||
dom.pasteText(r.sc, r.so, text);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -129,10 +129,12 @@ var MediaDialog = Dialog.extend({
|
||||
this.imageDialog.appendTo(this.$("#editor-media-image"));
|
||||
this.documentDialog = new ImageDialog(this, this.media, _.extend({'document': true}, this.options));
|
||||
this.documentDialog.appendTo(this.$("#editor-media-document"));
|
||||
this.iconDialog = new fontIconsDialog(this, this.media, this.options);
|
||||
this.iconDialog.appendTo(this.$("#editor-media-icon"));
|
||||
this.videoDialog = new VideoDialog(this, this.media, this.options);
|
||||
this.videoDialog.appendTo(this.$("#editor-media-video"));
|
||||
if (!this.only_images) {
|
||||
this.iconDialog = new fontIconsDialog(this, this.media, this.options);
|
||||
this.iconDialog.appendTo(this.$("#editor-media-icon"));
|
||||
this.videoDialog = new VideoDialog(this, this.media, this.options);
|
||||
this.videoDialog.appendTo(this.$("#editor-media-video"));
|
||||
}
|
||||
|
||||
this.active = this.imageDialog;
|
||||
|
||||
@@ -175,10 +177,11 @@ var MediaDialog = Dialog.extend({
|
||||
if (this.active !== this.documentDialog) {
|
||||
this.documentDialog.clear();
|
||||
}
|
||||
if (this.active !== this.iconDialog) {
|
||||
// if not mode only_images
|
||||
if (this.iconDialog && this.active !== this.iconDialog) {
|
||||
this.iconDialog.clear();
|
||||
}
|
||||
if (this.active !== this.videoDialog) {
|
||||
if (this.videoDialog && this.active !== this.videoDialog) {
|
||||
this.videoDialog.clear();
|
||||
}
|
||||
} else {
|
||||
@@ -574,7 +577,7 @@ var getCssSelectors = function(filter) {
|
||||
}
|
||||
return cacheCssSelectors[filter] = css;
|
||||
};
|
||||
function computeFonts() {
|
||||
var computeFonts = _.once(function() {
|
||||
_.each(fontIcons, function (data) {
|
||||
data.cssData = getCssSelectors(data.parser);
|
||||
data.alias = [];
|
||||
@@ -583,7 +586,7 @@ function computeFonts() {
|
||||
return css[2];
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
rte.Class.include({
|
||||
init: function (EditorBar) {
|
||||
@@ -632,9 +635,7 @@ var fontIconsDialog = Widget.extend({
|
||||
this._super();
|
||||
this.parent = parent;
|
||||
this.media = media;
|
||||
if (_.find(fontIcons, function (data) {return !data.icons;})) {
|
||||
computeFonts();
|
||||
}
|
||||
computeFonts();
|
||||
},
|
||||
start: function () {
|
||||
return this._super().then(this.proxy('load_data'));
|
||||
|
||||
@@ -46,6 +46,18 @@ var AbstractField = Widget.extend(FieldInterface, {
|
||||
},
|
||||
});
|
||||
|
||||
var FormatChar = 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));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var KanbanPriority = AbstractField.extend({
|
||||
init: function(parent, field, $node) {
|
||||
this._super.apply(this, arguments);
|
||||
@@ -233,7 +245,9 @@ fields_registry
|
||||
.add('priority', KanbanPriority)
|
||||
.add('kanban_state_selection', KanbanSelection)
|
||||
.add("attachment_image", KanbanAttachmentImage)
|
||||
.add('progress', KanbanProgressBar);
|
||||
.add('progress', KanbanProgressBar)
|
||||
.add('float_time', FormatChar)
|
||||
;
|
||||
|
||||
return {
|
||||
AbstractField: AbstractField,
|
||||
|
||||
@@ -29,10 +29,10 @@ var Theme = Widget.extend({
|
||||
template: 'website.theme_customize',
|
||||
events: {
|
||||
'change input[data-xmlid],input[data-enable],input[data-disable]': 'change_selection',
|
||||
'click label:has(input[data-xmlid],input[data-enable],input[data-disable])': function (event) {
|
||||
'mousedown label:has(input[data-xmlid],input[data-enable],input[data-disable])': function (event) {
|
||||
var self = this;
|
||||
this.time_select = _.defer(function () {
|
||||
self.on_select($(event.target).find('input'));
|
||||
self.on_select($(event.target).find('input'), event);
|
||||
});
|
||||
},
|
||||
'click .close': 'close',
|
||||
@@ -198,7 +198,7 @@ var Theme = Widget.extend({
|
||||
|
||||
if (this.$el.hasClass("loading")) return; // prevent to change selection when css is loading
|
||||
|
||||
var $option = $(event.target),
|
||||
var $option = $(event.target).is('input') ? $(event.target) : $("input", event.target),
|
||||
$options = $option,
|
||||
checked = $option.prop("checked");
|
||||
|
||||
@@ -252,13 +252,13 @@ var Theme = Widget.extend({
|
||||
clearTimeout(this.timer);
|
||||
if (this.flag) {
|
||||
this.timer = _.defer(function () {
|
||||
if (!init_mode) self.on_select($options);
|
||||
if (!init_mode) self.on_select($options, event);
|
||||
self.update_style(self.get_xml_ids($enable), self.get_xml_ids($disable), self.reload);
|
||||
self.reload = false;
|
||||
});
|
||||
} else {
|
||||
this.timer = _.defer(function () {
|
||||
if (!init_mode) self.on_select($options);
|
||||
if (!init_mode) self.on_select($options, event);
|
||||
self.reload = false;
|
||||
});
|
||||
}
|
||||
@@ -266,9 +266,8 @@ var Theme = Widget.extend({
|
||||
/* Method call when the user change the selection or click on an input
|
||||
* @values: all changed inputs
|
||||
*/
|
||||
on_select: function ($inputs) {
|
||||
on_select: function ($inputs, event) {
|
||||
clearTimeout(this.time_select);
|
||||
console.log($inputs.get());
|
||||
},
|
||||
click: function (event) {
|
||||
if (!$(event.target).closest("#theme_customize_modal > *").length) {
|
||||
|
||||
@@ -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 & <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 = '<script>1 && "hello & world"</script>'
|
||||
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')
|
||||
|
||||
@@ -262,7 +262,8 @@ if(!$('.website_forum').length) {
|
||||
['style', ['style']],
|
||||
['font', ['bold', 'italic', 'underline', 'clear']],
|
||||
['para', ['ul', 'ol', 'paragraph']],
|
||||
['table', ['table']]
|
||||
['table', ['table']],
|
||||
['history', ['undo', 'redo']],
|
||||
];
|
||||
if (parseInt($("#karma").val()) >= editor_karma) {
|
||||
toolbar.push(['insert', ['link', 'picture']]);
|
||||
|
||||
@@ -92,7 +92,8 @@ class WebsiteMail(http.Controller):
|
||||
partner_ids = _object._find_partner_from_emails(
|
||||
cr, SUPERUSER_ID, _id, [email], context=context, check_followers=True)
|
||||
if not partner_ids or not partner_ids[0]:
|
||||
partner_ids = [partner_obj.create(cr, SUPERUSER_ID, {'name': email, 'email': email}, context=context)]
|
||||
name = email.split('@')[0]
|
||||
partner_ids = [partner_obj.create(cr, SUPERUSER_ID, {'name': name, 'email': email}, context=context)]
|
||||
|
||||
# add or remove follower
|
||||
if _message_is_follower:
|
||||
|
||||
@@ -36,7 +36,7 @@ class sale_quote(http.Controller):
|
||||
days = (datetime.datetime.strptime(order.validity_date, '%Y-%m-%d') - datetime.datetime.now()).days + 1
|
||||
if pdf:
|
||||
report_obj = request.registry['report']
|
||||
pdf = report_obj.get_pdf(request.cr, SUPERUSER_ID, [order_id], 'website_quote.report_quote', data=None, context=request.context)
|
||||
pdf = report_obj.get_pdf(request.cr, SUPERUSER_ID, [order_id], 'website_quote.report_quote', data=None, context=dict(request.context, set_viewport_size=True))
|
||||
pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, request.uid, context=request.context)
|
||||
@@ -141,7 +141,7 @@ class sale_quote(http.Controller):
|
||||
res = request.registry.get('sale.order.line').product_id_change(request.cr, SUPERUSER_ID, order_id,
|
||||
False, option.product_id.id, option.quantity, option.uom_id.id, option.quantity, option.uom_id.id,
|
||||
option.name, order.partner_id.id, False, True, time.strftime('%Y-%m-%d'),
|
||||
False, order.fiscal_position_id.id, True, request.context)
|
||||
False, order.fiscal_position_id.id, True, dict(request.context or {}, company_id=order.company_id.id))
|
||||
vals = res.get('value', {})
|
||||
if 'tax_id' in vals:
|
||||
vals['tax_id'] = [(6, 0, vals['tax_id'])]
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<xpath expr="//field[@name='project_id']" position="before">
|
||||
<label for="template_id"/>
|
||||
<div>
|
||||
<field name="template_id" on_change="onchange_template_id(template_id, partner_id, fiscal_position_id, pricelist_id)"/>
|
||||
<field name="template_id" context="{'company_id': company_id}" on_change="onchange_template_id(template_id, partner_id, fiscal_position_id, pricelist_id)"/>
|
||||
<button name="open_quotation" type="object"
|
||||
class="oe_link o_open_quotation" string="View Online Quotation"
|
||||
attrs="{'invisible': [('template_id', '=', False)]}"/>
|
||||
|
||||
@@ -264,6 +264,7 @@ class website_sale(http.Controller):
|
||||
|
||||
if category:
|
||||
category = category_obj.browse(cr, uid, int(category), context=context)
|
||||
category = category if category.exists() else False
|
||||
|
||||
attrib_list = request.httprequest.args.getlist('attrib')
|
||||
attrib_values = [map(int,v.split("-")) for v in attrib_list if v]
|
||||
|
||||
@@ -72,7 +72,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>
|
||||
<t t-if="show_publish">
|
||||
@@ -340,7 +340,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" id="product_details">
|
||||
<h1 itemprop="name" t-field="product.name">Product Name</h1>
|
||||
|
||||
+18
-2
@@ -626,9 +626,9 @@ instead of a single view its ``arch`` field is composed of any number of
|
||||
<field name="model">idea.category</field>
|
||||
<field name="inherit_id" ref="id_category_list"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- find field description inside tree, and add the field
|
||||
<!-- find field description and add the field
|
||||
idea_ids after it -->
|
||||
<xpath expr="/tree/field[@name='description']" position="after">
|
||||
<xpath expr="//field[@name='description']" position="after">
|
||||
<field name="idea_ids" string="Number of ideas"/>
|
||||
</xpath>
|
||||
</field>
|
||||
@@ -652,6 +652,22 @@ instead of a single view its ``arch`` field is composed of any number of
|
||||
alters the attributes of the matched element using special
|
||||
``attribute`` elements in the ``xpath``'s body
|
||||
|
||||
.. tip::
|
||||
|
||||
When matching a single element, the ``position`` attribute can be set directly
|
||||
on the element to be found. Both inheritances below will give the same result.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<xpath expr="//field[@name='description']" position="after">
|
||||
<field name="idea_ids" />
|
||||
</xpath>
|
||||
|
||||
<field name="description" position="after">
|
||||
<field name="idea_ids" />
|
||||
</field>
|
||||
|
||||
|
||||
.. exercise:: Alter existing content
|
||||
|
||||
* Using model inheritance, modify the existing *Partner* model to add an
|
||||
|
||||
@@ -237,9 +237,8 @@ class ir_attachment(osv.osv):
|
||||
""" compute the checksum for the given datas
|
||||
:param bin_data : datas in its binary form
|
||||
"""
|
||||
if bin_data:
|
||||
return hashlib.sha1(bin_data).hexdigest()
|
||||
return False
|
||||
# an empty file has a checksum too (for caching)
|
||||
return hashlib.sha1(bin_data or '').hexdigest()
|
||||
|
||||
def _compute_mimetype(self, values):
|
||||
""" compute the mimetype of the given values
|
||||
|
||||
@@ -106,6 +106,7 @@ class ir_http(osv.AbstractModel):
|
||||
wdate = attach[0]['__last_update']
|
||||
datas = attach[0]['datas'] or ''
|
||||
name = attach[0]['name']
|
||||
checksum = attach[0]['checksum'] or hashlib.sha1(datas).hexdigest()
|
||||
|
||||
if (not datas and name != request.httprequest.path and
|
||||
name.startswith(('http://', 'https://', '/'))):
|
||||
@@ -119,7 +120,7 @@ class ir_http(osv.AbstractModel):
|
||||
# just in case we have a timestamp without microseconds
|
||||
response.last_modified = datetime.datetime.strptime(wdate, server_format)
|
||||
|
||||
response.set_etag(attach[0]['checksum'])
|
||||
response.set_etag(checksum)
|
||||
response.make_conditional(request.httprequest)
|
||||
|
||||
if response.status_code == 304:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
import pytz
|
||||
import time
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
@@ -194,22 +195,14 @@ class ir_sequence(models.Model):
|
||||
|
||||
def _interpolation_dict():
|
||||
if self.env.context.get('ir_sequence_date'):
|
||||
t = time.strptime(self.env.context.get('ir_sequence_date'), '%Y-%m-%d')
|
||||
t = datetime.strptime(self.env.context.get('ir_sequence_date'), '%Y-%m-%d')
|
||||
else:
|
||||
t = time.localtime() # Actually, the server is always in UTC.
|
||||
return {
|
||||
'year': time.strftime('%Y', t),
|
||||
'month': time.strftime('%m', t),
|
||||
'day': time.strftime('%d', t),
|
||||
'y': time.strftime('%y', t),
|
||||
'doy': time.strftime('%j', t),
|
||||
'woy': time.strftime('%W', t),
|
||||
'weekday': time.strftime('%w', t),
|
||||
'h24': time.strftime('%H', t),
|
||||
'h12': time.strftime('%I', t),
|
||||
'min': time.strftime('%M', t),
|
||||
'sec': time.strftime('%S', t),
|
||||
t = datetime.now(pytz.timezone(self.env.context.get('tz') or 'UTC'))
|
||||
sequences = {
|
||||
'year': '%Y', 'month': '%m', 'day': '%d', 'y': '%y', 'doy': '%j', 'woy': '%W',
|
||||
'weekday': '%w', 'h24': '%H', 'h12': '%I', 'min': '%M', 'sec': '%S'
|
||||
}
|
||||
return {key: t.strftime(sequence) for key, sequence in sequences.iteritems()}
|
||||
|
||||
d = _interpolation_dict()
|
||||
try:
|
||||
|
||||
@@ -9,6 +9,7 @@ from openerp import api, fields as fields2
|
||||
from openerp import tools
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import float_round, float_is_zero, float_compare
|
||||
import simplejson as json
|
||||
|
||||
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
|
||||
|
||||
@@ -252,9 +253,9 @@ class res_currency(osv.osv):
|
||||
symbol = row['symbol'] or row['name']
|
||||
format_number_str = "openerp.web.format_value(arguments[0], {type: 'float', digits: [69," + str(row['decimal_places']) + "]}, 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 + " }"
|
||||
if (row['id'] == company_currency_id):
|
||||
company_currency_format = return_str
|
||||
|
||||
@@ -461,6 +461,10 @@ def html2plaintext(html, body_id=None, encoding='utf-8'):
|
||||
## download here: http://www.peterbe.com/plog/html2plaintext
|
||||
|
||||
html = ustr(html)
|
||||
|
||||
if not html:
|
||||
return ''
|
||||
|
||||
tree = etree.fromstring(html, parser=etree.HTMLParser())
|
||||
|
||||
if body_id is not None:
|
||||
|
||||
Reference in New Issue
Block a user