[MERGE] forward port of branch 8.0 up to 22bd3bd
This commit is contained in:
@@ -203,7 +203,7 @@ class account_analytic_line(osv.osv):
|
||||
unit_price = total_price*-1.0 / total_qty
|
||||
|
||||
factor = self.pool['hr_timesheet_invoice.factor'].browse(cr, uid, factor_id, context=uom_context)
|
||||
factor_name = factor.customer_name
|
||||
factor_name = factor.customer_name or ''
|
||||
curr_invoice_line = {
|
||||
'price_unit': unit_price,
|
||||
'quantity': total_qty,
|
||||
|
||||
@@ -73,7 +73,7 @@ class hr_timesheet_report(osv.osv):
|
||||
htss.state"""
|
||||
|
||||
def _from(self):
|
||||
return super(hr_timesheet_report, self)._from() + "left join hr_timesheet_sheet_sheet as htss ON (hat.sheet_id=htss.id) join totals as t on (t.sheet_id = hat.sheet_id and t.date = aal.date)"
|
||||
return super(hr_timesheet_report, self)._from() + "left join hr_timesheet_sheet_sheet as htss ON (hat.sheet_id=htss.id) left join totals as t on (t.sheet_id = hat.sheet_id and t.date = aal.date)"
|
||||
|
||||
def _group_by(self):
|
||||
return super(hr_timesheet_report, self)._group_by() + """,
|
||||
|
||||
@@ -33,14 +33,14 @@ class procurement_order(osv.osv):
|
||||
self.check(cr, uid, [procurement_id], context=context)
|
||||
return procurement_id
|
||||
|
||||
def run(self, cr, uid, ids, context=None):
|
||||
def run(self, cr, uid, ids, autocommit=False, context=None):
|
||||
context = dict(context or {}, procurement_autorun_defer=True)
|
||||
res = super(procurement_order, self).run(cr, uid, ids, context=context)
|
||||
res = super(procurement_order, self).run(cr, uid, ids, autocommit=autocommit, context=context)
|
||||
|
||||
procurement_ids = self.search(cr, uid, [('move_dest_id.procurement_id', 'in', ids)], order='id', context=context)
|
||||
|
||||
if procurement_ids:
|
||||
return self.run(cr, uid, procurement_ids, context=context)
|
||||
return self.run(cr, uid, procurement_ids, autocommit=autocommit, context=context)
|
||||
return res
|
||||
|
||||
class stock_move(osv.osv):
|
||||
|
||||
@@ -56,8 +56,10 @@ class stock_history(osv.osv):
|
||||
for product_id in ids:
|
||||
line_ids.add(product_id)
|
||||
line_ids = list(line_ids)
|
||||
cr.execute('SELECT id, product_id, price_unit_on_quant, company_id, quantity FROM stock_history WHERE id in %s', (tuple(line_ids),))
|
||||
lines_rec = cr.dictfetchall()
|
||||
lines_rec = {}
|
||||
if line_ids:
|
||||
cr.execute('SELECT id, product_id, price_unit_on_quant, company_id, quantity FROM stock_history WHERE id in %s', (tuple(line_ids),))
|
||||
lines_rec = cr.dictfetchall()
|
||||
lines_dict = dict((line['id'], line) for line in lines_rec)
|
||||
product_ids = list(set(line_rec['product_id'] for line_rec in lines_rec))
|
||||
products_rec = self.pool['product.product'].read(cr, uid, product_ids, ['cost_method', 'product_tmpl_id'], context=context)
|
||||
|
||||
@@ -565,7 +565,7 @@ openerp.web_calendar = function(instance) {
|
||||
attendee_showed += 1;
|
||||
if (attendee_showed<= MAX_ATTENDEES) {
|
||||
if (self.avatar_model !== null) {
|
||||
the_title_avatar += '<img title="' + self.all_attendees[the_attendee_people] + '" class="attendee_head" \
|
||||
the_title_avatar += '<img title="' + _.escape(self.all_attendees[the_attendee_people]) + '" class="attendee_head" \
|
||||
src="/web/binary/image?model=' + self.avatar_model + '&field=image_small&id=' + the_attendee_people + '"></img>';
|
||||
}
|
||||
else {
|
||||
@@ -573,12 +573,12 @@ openerp.web_calendar = function(instance) {
|
||||
tempColor = (self.all_filters[the_attendee_people] !== undefined)
|
||||
? self.all_filters[the_attendee_people].color
|
||||
: (self.all_filters[-1] ? self.all_filters[-1].color : 1);
|
||||
the_title_avatar += '<i class="fa fa-user attendee_head color_'+tempColor+'" title="' + self.all_attendees[the_attendee_people] + '" ></i>';
|
||||
the_title_avatar += '<i class="fa fa-user attendee_head color_'+tempColor+'" title="' + _.escape(self.all_attendees[the_attendee_people]) + '" ></i>';
|
||||
}//else don't add myself
|
||||
}
|
||||
}
|
||||
else {
|
||||
attendee_other += self.all_attendees[the_attendee_people] +", ";
|
||||
attendee_other += _.escape(self.all_attendees[the_attendee_people]) +", ";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
if (!this.$target.has('.media_iframe_video_size')) {
|
||||
var editor = '<div class="css_editable_mode_display"> </div>';
|
||||
var size = '<div class="media_iframe_video_size"> </div>';
|
||||
this.$target.html(editor+size+'<iframe src="'+this.$target.data("src")+'" frameborder="0" allowfullscreen="allowfullscreen"></iframe>');
|
||||
this.$target.html(editor+size+'<iframe src="'+_.escape(this.$target.data("src"))+'" frameborder="0" allowfullscreen="allowfullscreen"></iframe>');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import json
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp.tools import html_escape as escape
|
||||
|
||||
|
||||
class google_map(http.Controller):
|
||||
@@ -48,12 +49,13 @@ class google_map(http.Controller):
|
||||
}
|
||||
request.context.update({'show_address': True})
|
||||
for partner in partner_obj.browse(cr, SUPERUSER_ID, partners_ids, context=context):
|
||||
# TODO in master, do not use `escape` but `t-esc` in the qweb template.
|
||||
partner_data["partners"].append({
|
||||
'id': partner.id,
|
||||
'name': partner.name,
|
||||
'address': '\n'.join(partner.name_get()[0][1].split('\n')[1:]),
|
||||
'latitude': partner.partner_latitude,
|
||||
'longitude': partner.partner_longitude,
|
||||
'name': escape(partner.name),
|
||||
'address': escape('\n'.join(partner.name_get()[0][1].split('\n')[1:])),
|
||||
'latitude': escape(str(partner.partner_latitude)),
|
||||
'longitude': escape(str(partner.partner_longitude)),
|
||||
})
|
||||
|
||||
# generate the map
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
Slovenia, 2015-06-29
|
||||
|
||||
Mentis d.o.o. agrees to the terms of the Odoo Corporate Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Dušan Laznik dusan.laznik@mentis.si https://github.com/laznikd
|
||||
|
||||
List of contributors:
|
||||
|
||||
Dušan Laznik dusan.laznik@mentis.si https://github.com/laznikd
|
||||
Aleksander Dirntiš sandi.dirntis@mentis.si https://github.com/adirntis
|
||||
Matjaž Kalič matjaz.kalic@mentis.si https://github.com/kalicm
|
||||
@@ -37,6 +37,19 @@ class ConversionNotFound(ValueError): pass
|
||||
class ir_fields_converter(models.Model):
|
||||
_name = 'ir.fields.converter'
|
||||
|
||||
@api.model
|
||||
def _format_import_error(self, error_type, error_msg, error_params=(), error_args=None):
|
||||
# sanitize error params for later formatting by the import system
|
||||
sanitize = lambda p: p.replace('%', '%%') if isinstance(p, basestring) else p
|
||||
if error_params:
|
||||
if isinstance(error_params, basestring):
|
||||
error_params = sanitize(error_params)
|
||||
elif isinstance(error_params, dict):
|
||||
error_params = dict((k, sanitize(v)) for k, v in error_params.iteritems())
|
||||
elif isinstance(error_params, tuple):
|
||||
error_params = tuple(map(sanitize, error_params))
|
||||
return error_type(error_msg % error_params, error_args)
|
||||
|
||||
@api.model
|
||||
def for_model(self, model, fromtype=str):
|
||||
""" Returns a converter object for the model. A converter is a
|
||||
@@ -148,29 +161,34 @@ class ir_fields_converter(models.Model):
|
||||
if value.lower() in falses:
|
||||
return False, []
|
||||
|
||||
return True, [ImportWarning(
|
||||
_(u"Unknown value '%s' for boolean field '%%(field)s', assuming '%s'")
|
||||
% (value, yes), {
|
||||
'moreinfo': _(u"Use '1' for yes and '0' for no")
|
||||
})]
|
||||
return True, [self._format_import_error(
|
||||
ImportWarning,
|
||||
_(u"Unknown value '%s' for boolean field '%%(field)s', assuming '%s'"),
|
||||
(value, yes),
|
||||
{'moreinfo': _(u"Use '1' for yes and '0' for no")}
|
||||
)]
|
||||
|
||||
@api.model
|
||||
def _str_to_integer(self, model, field, value):
|
||||
try:
|
||||
return int(value), []
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
_(u"'%s' does not seem to be an integer for field '%%(field)s'")
|
||||
% value)
|
||||
raise self._format_import_error(
|
||||
ValueError,
|
||||
_(u"'%s' does not seem to be an integer for field '%%(field)s'"),
|
||||
value
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _str_to_float(self, model, field, value):
|
||||
try:
|
||||
return float(value), []
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
_(u"'%s' does not seem to be a number for field '%%(field)s'")
|
||||
% value)
|
||||
raise self._format_import_error(
|
||||
ValueError,
|
||||
_(u"'%s' does not seem to be a number for field '%%(field)s'"),
|
||||
value
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _str_id(self, model, field, value):
|
||||
@@ -184,10 +202,12 @@ class ir_fields_converter(models.Model):
|
||||
time.strptime(value, DEFAULT_SERVER_DATE_FORMAT)
|
||||
return value, []
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
_(u"'%s' does not seem to be a valid date for field '%%(field)s'") % value, {
|
||||
'moreinfo': _(u"Use the format '%s'") % u"2012-12-31"
|
||||
})
|
||||
raise self._format_import_error(
|
||||
ValueError,
|
||||
_(u"'%s' does not seem to be a valid date for field '%%(field)s'"),
|
||||
value,
|
||||
{'moreinfo': _(u"Use the format '%s'") % u"2012-12-31"}
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _input_tz(self):
|
||||
@@ -215,10 +235,12 @@ class ir_fields_converter(models.Model):
|
||||
parsed_value = datetime.datetime.strptime(
|
||||
value, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
_(u"'%s' does not seem to be a valid datetime for field '%%(field)s'") % value, {
|
||||
'moreinfo': _(u"Use the format '%s'") % u"2012-12-31 23:59:59"
|
||||
})
|
||||
raise self._format_import_error(
|
||||
ValueError,
|
||||
_(u"'%s' does not seem to be a valid datetime for field '%%(field)s'"),
|
||||
value,
|
||||
{'moreinfo': _(u"Use the format '%s'") % u"2012-12-31 23:59:59"}
|
||||
)
|
||||
|
||||
input_tz = self._input_tz()# Apply input tz to the parsed naive datetime
|
||||
dt = input_tz.localize(parsed_value, is_dst=False)
|
||||
@@ -251,12 +273,12 @@ class ir_fields_converter(models.Model):
|
||||
if value == unicode(item) or value in labels:
|
||||
return item, []
|
||||
|
||||
raise ValueError(
|
||||
_(u"Value '%s' not found in selection field '%%(field)s'") % (
|
||||
value), {
|
||||
'moreinfo': [_label or unicode(item) for item, _label in selection
|
||||
if _label or item]
|
||||
})
|
||||
raise self._format_import_error(
|
||||
ValueError,
|
||||
_(u"Value '%s' not found in selection field '%%(field)s'"),
|
||||
value,
|
||||
{'moreinfo': [_label or unicode(item) for item, _label in selection if _label or item]}
|
||||
)
|
||||
|
||||
@api.model
|
||||
def db_id_for(self, model, field, subfield, value):
|
||||
@@ -298,8 +320,10 @@ class ir_fields_converter(models.Model):
|
||||
id = tentative_id
|
||||
except psycopg2.DataError:
|
||||
# type error
|
||||
raise ValueError(
|
||||
_(u"Invalid database id '%s' for the field '%%(field)s'") % value,
|
||||
raise self._format_import_error(
|
||||
ValueError,
|
||||
_(u"Invalid database id '%s' for the field '%%(field)s'"),
|
||||
value,
|
||||
{'moreinfo': action})
|
||||
elif subfield == 'id':
|
||||
field_type = _(u"external id")
|
||||
@@ -321,12 +345,17 @@ class ir_fields_converter(models.Model):
|
||||
% (len(ids))))
|
||||
id, _name = ids[0]
|
||||
else:
|
||||
raise Exception(_(u"Unknown sub-field '%s'") % subfield)
|
||||
raise self._format_import_error(
|
||||
Exception,
|
||||
_(u"Unknown sub-field '%s'"),
|
||||
subfield
|
||||
)
|
||||
|
||||
if id is None:
|
||||
raise ValueError(
|
||||
_(u"No matching record found for %(field_type)s '%(value)s' in field '%%(field)s'")
|
||||
% {'field_type': field_type, 'value': value},
|
||||
raise self._format_import_error(
|
||||
ValueError,
|
||||
_(u"No matching record found for %(field_type)s '%(value)s' in field '%%(field)s'"),
|
||||
{'field_type': field_type, 'value': value},
|
||||
{'moreinfo': action})
|
||||
return id, field_type, warnings
|
||||
|
||||
|
||||
@@ -111,6 +111,12 @@ class test_ids_stuff(ImporterCase):
|
||||
self.import_(['id', 'value'], [['somexmlid', '1234567']])
|
||||
self.assertEqual([1234567], values(self.read()))
|
||||
|
||||
def test_wrong_format(self):
|
||||
self.assertEqual(
|
||||
self.import_(['value'], [['50%']]),
|
||||
error(1, u"'50%' does not seem to be an integer for field 'unknown'"))
|
||||
|
||||
|
||||
class test_boolean_field(ImporterCase):
|
||||
model_name = 'export.boolean'
|
||||
|
||||
@@ -163,10 +169,13 @@ class test_boolean_field(ImporterCase):
|
||||
# Problem: OpenOffice (and probably excel) output localized booleans
|
||||
['VRAI'],
|
||||
[u'OFF'],
|
||||
[u'是的'],
|
||||
['!&%#${}'],
|
||||
['%(field)s'],
|
||||
]),
|
||||
ok(8))
|
||||
ok(11))
|
||||
self.assertEqual(
|
||||
[True] * 8,
|
||||
[True] * 11,
|
||||
values(self.read()))
|
||||
|
||||
class test_integer_field(ImporterCase):
|
||||
|
||||
+16
-17
@@ -30,7 +30,7 @@ import logging
|
||||
import pytz
|
||||
import xmlrpclib
|
||||
|
||||
from openerp.tools import float_round, frozendict, html_sanitize, ustr
|
||||
from openerp.tools import float_round, frozendict, html_sanitize, ustr, OrderedSet
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
|
||||
|
||||
@@ -1606,34 +1606,33 @@ class _RelationalMulti(_Relational):
|
||||
return value.with_env(record.env)
|
||||
elif isinstance(value, list):
|
||||
# value is a list of record ids or commands
|
||||
if not record.id:
|
||||
record = record.browse() # new record has no value
|
||||
result = record[self.name]
|
||||
# modify result with the commands;
|
||||
# beware to not introduce duplicates in result
|
||||
comodel = record.env[self.comodel_name]
|
||||
ids = OrderedSet(record[self.name].ids)
|
||||
# modify ids with the commands
|
||||
for command in value:
|
||||
if isinstance(command, (tuple, list)):
|
||||
if command[0] == 0:
|
||||
result += result.new(command[2])
|
||||
ids.add(comodel.new(command[2]).id)
|
||||
elif command[0] == 1:
|
||||
result.browse(command[1]).update(command[2])
|
||||
result += result.browse(command[1]) - result
|
||||
comodel.browse(command[1]).update(command[2])
|
||||
ids.add(command[1])
|
||||
elif command[0] == 2:
|
||||
# note: the record will be deleted by write()
|
||||
result -= result.browse(command[1])
|
||||
ids.discard(command[1])
|
||||
elif command[0] == 3:
|
||||
result -= result.browse(command[1])
|
||||
ids.discard(command[1])
|
||||
elif command[0] == 4:
|
||||
result += result.browse(command[1]) - result
|
||||
ids.add(command[1])
|
||||
elif command[0] == 5:
|
||||
result = result.browse()
|
||||
ids.clear()
|
||||
elif command[0] == 6:
|
||||
result = result.browse(command[2])
|
||||
ids = OrderedSet(command[2])
|
||||
elif isinstance(command, dict):
|
||||
result += result.new(command)
|
||||
ids.add(comodel.new(command).id)
|
||||
else:
|
||||
result += result.browse(command) - result
|
||||
return result
|
||||
ids.add(command)
|
||||
# return result as a recordset
|
||||
return comodel.browse(list(ids))
|
||||
elif not value:
|
||||
return self.null(record.env)
|
||||
raise ValueError("Wrong value for %s: %s" % (self, value))
|
||||
|
||||
+2
-5
@@ -1194,11 +1194,8 @@ class BaseModel(object):
|
||||
type = 'warning' if isinstance(exception, Warning) else 'error'
|
||||
# logs the logical (not human-readable) field name for automated
|
||||
# processing of response, but injects human readable in message
|
||||
# Before applying string substitution, we need to escape all '%'
|
||||
# characters that are not part of substitutable reference.
|
||||
regex = '%%(?!\((%s)\)s)' % '|'.join(base.keys())
|
||||
message = unicode(re.sub(regex, '%%', exception.args[0])) % base
|
||||
record = dict(base, type=type, field=field, message=message)
|
||||
record = dict(base, type=type, field=field,
|
||||
message=unicode(exception.args[0]) % base)
|
||||
if len(exception.args) > 1 and exception.args[1]:
|
||||
record.update(exception.args[1])
|
||||
log(record)
|
||||
|
||||
+12
-1
@@ -37,7 +37,7 @@ import threading
|
||||
import time
|
||||
import werkzeug.utils
|
||||
import zipfile
|
||||
from collections import defaultdict, Mapping
|
||||
from collections import defaultdict, Mapping, OrderedDict
|
||||
from datetime import datetime
|
||||
from itertools import islice, izip, groupby
|
||||
from lxml import etree
|
||||
@@ -1136,6 +1136,17 @@ class frozendict(dict):
|
||||
def update(self, *args, **kwargs):
|
||||
raise NotImplementedError("'update' not supported on frozendict")
|
||||
|
||||
class OrderedSet(OrderedDict):
|
||||
""" A simple collection that remembers the elements insertion order. """
|
||||
def __init__(self, seq=()):
|
||||
super(OrderedSet, self).__init__([(x, None) for x in seq])
|
||||
|
||||
def add(self, elem):
|
||||
self[elem] = None
|
||||
|
||||
def discard(self, elem):
|
||||
self.pop(elem, None)
|
||||
|
||||
@contextmanager
|
||||
def ignore(*exc):
|
||||
try:
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ Jinja2==2.7.3
|
||||
Mako==1.0.1
|
||||
MarkupSafe==0.23
|
||||
Pillow==2.7.0
|
||||
http://download.gna.org/pychart/PyChart-1.39.tar.gz#egg=PyChart
|
||||
Python-Chart==1.39
|
||||
PyYAML==3.11
|
||||
Werkzeug==0.9.6
|
||||
argparse==1.2.1
|
||||
|
||||
@@ -87,7 +87,7 @@ def py2exe_options():
|
||||
'PIL',
|
||||
'poplib',
|
||||
'psutil',
|
||||
'pychart',
|
||||
'python-chart',
|
||||
'pydot',
|
||||
'pyparsing',
|
||||
'pyPdf',
|
||||
@@ -128,7 +128,6 @@ setup(
|
||||
packages=find_packages(),
|
||||
package_dir={'%s' % lib_name: 'openerp'},
|
||||
include_package_data=True,
|
||||
dependency_links=['http://download.gna.org/pychart/'],
|
||||
install_requires=[
|
||||
'babel >= 1.0',
|
||||
'decorator',
|
||||
@@ -144,7 +143,7 @@ setup(
|
||||
'psutil', # windows binary code.google.com/p/psutil/downloads/list
|
||||
'psycogreen',
|
||||
'psycopg2 >= 2.2',
|
||||
'pychart', # not on pypi, use: pip install http://download.gna.org/pychart/PyChart-1.39.tar.gz
|
||||
'python-chart',
|
||||
'pydot',
|
||||
'pyparsing < 2',
|
||||
'pypdf',
|
||||
|
||||
Reference in New Issue
Block a user