diff --git a/addons/auth_oauth/data/auth_oauth_data.xml b/addons/auth_oauth/data/auth_oauth_data.xml index 1b1ff532ae4..8417c9c4b2d 100644 --- a/addons/auth_oauth/data/auth_oauth_data.xml +++ b/addons/auth_oauth/data/auth_oauth_data.xml @@ -16,7 +16,7 @@ https://www.facebook.com/dialog/oauth public_profile,email https://graph.facebook.com/me - + https://graph.facebook.com/me?fields=id,name,email fa fa-facebook-square Log in with facebook diff --git a/addons/base_sparse_field/models/fields.py b/addons/base_sparse_field/models/fields.py index cb1e1981193..e2b8adc0e29 100644 --- a/addons/base_sparse_field/models/fields.py +++ b/addons/base_sparse_field/models/fields.py @@ -81,7 +81,7 @@ class Serialized(fields.Field): } column_type = ('text', 'text') - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): return json.dumps(value) def convert_to_cache(self, value, record, validate=True): diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index b70b3517bf2..f777dc27324 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -301,7 +301,7 @@ var PosDB = core.Class.extend({ search_partner: function(query){ try { query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.'); - query = query.replace(' ','.+'); + query = query.replace(/ /g,'.+'); var re = RegExp("([0-9]+):.*?"+query,"gi"); }catch(e){ return []; diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index 401c6d6c8cb..02dcc93554d 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -24,7 +24,7 @@ class CrmTeam(models.Model): def _get_default_team_id(self, user_id=None): if not user_id: user_id = self.env.uid - company_id = self.sudo(user_id).company_id.id + company_id = self.sudo(user_id).env.user.company_id.id team_id = self.env['crm.team'].sudo().search([ '|', ('user_id', '=', user_id), ('member_ids', '=', user_id), '|', ('company_id', '=', False), ('company_id', 'child_of', [company_id]) diff --git a/addons/web/static/src/js/views/search_view.js b/addons/web/static/src/js/views/search_view.js index 65dbb4557e7..d11af68c107 100644 --- a/addons/web/static/src/js/views/search_view.js +++ b/addons/web/static/src/js/views/search_view.js @@ -337,7 +337,7 @@ var SearchView = View.extend({ }, set_default_filters: function () { var self = this, - default_custom_filter = this.$buttons && this.favorite_menu.get_default_filter(); + default_custom_filter = this.$buttons && this.favorite_menu && this.favorite_menu.get_default_filter(); if (!self.options.disable_custom_filters && default_custom_filter) { return this.favorite_menu.toggle_filter(default_custom_filter, true); } diff --git a/doc/cla/individual/ruter.md b/doc/cla/individual/ruter.md new file mode 100644 index 00000000000..1560ba2b7f3 --- /dev/null +++ b/doc/cla/individual/ruter.md @@ -0,0 +1,11 @@ +China, 2017-12-05 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Ruter ruter.lv@gmail.com https://github.com/ruter diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index 94bc0e23c55..a6af588468a 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -5,7 +5,7 @@ from datetime import date, datetime from odoo.exceptions import AccessError, except_orm from odoo.tests import common -from odoo.tools import mute_logger +from odoo.tools import mute_logger, float_repr class TestFields(common.TransactionCase): @@ -305,21 +305,65 @@ class TestFields(common.TransactionCase): record.number = 2.4999999999999996 self.assertEqual(record.number, 2.50) + def check_monetary(self, record, amount, currency, msg=None): + # determine the possible roundings of amount + if currency: + ramount = currency.round(amount) + samount = float(float_repr(ramount, currency.decimal_places)) + else: + ramount = samount = amount + + # check the currency on record + self.assertEqual(record.currency_id, currency) + + # check the value on the record + self.assertIn(record.amount, [ramount, samount], msg) + + # check the value in the database + self.cr.execute('SELECT amount FROM test_new_api_mixed WHERE id=%s', [record.id]) + value = self.cr.fetchone()[0] + self.assertEqual(value, samount, msg) + def test_20_monetary(self): """ test monetary fields """ - record = self.env['test_new_api.mixed'].create({}) - self.assertTrue(record.currency_id) - self.assertEqual(record.currency_id.rounding, 0.01) + model = self.env['test_new_api.mixed'] + currency = self.env['res.currency'].with_context(active_test=False) + amount = 14.70126 - # the conversion to cache should round the value to 14.700000000000001 - record.amount = 14.7 - self.assertNotEqual(record.amount, 14.7) - self.assertEqual(record.amount, 14.700000000000001) + for rounding in [0.01, 0.0001, 1.0, 0]: + # first retrieve a currency corresponding to rounding + if rounding: + currency = currency.search([('rounding', '=', rounding)], limit=1) + self.assertTrue(currency, "No currency found for rounding %s" % rounding) + else: + # rounding=0 corresponds to currency=False + currency = currency.browse() - # however when stored to database, it should be serialized as 14.70 - self.cr.execute('SELECT amount FROM test_new_api_mixed WHERE id=%s', (record.id,)) - (amount,) = self.cr.fetchone() - self.assertEqual(amount, 14.7) + # case 1: create with amount and currency + record = model.create({'amount': amount, 'currency_id': currency.id}) + self.check_monetary(record, amount, currency, 'create(amount, currency)') + + # case 2: assign amount + record.amount = 0 + record.amount = amount + self.check_monetary(record, amount, currency, 'assign(amount)') + + # case 3: write with amount and currency + record.write({'amount': 0, 'currency_id': False}) + record.write({'amount': amount, 'currency_id': currency.id}) + self.check_monetary(record, amount, currency, 'write(amount, currency)') + + # case 4: write with amount only + record.write({'amount': 0}) + record.write({'amount': amount}) + self.check_monetary(record, amount, currency, 'write(amount)') + + # case 5: write with amount on several records + records = record + model.create({'currency_id': currency.id}) + records.write({'amount': 0}) + records.write({'amount': amount}) + for record in records: + self.check_monetary(record, amount, currency, 'multi write(amount)') def test_21_date(self): """ test date fields """ diff --git a/odoo/fields.py b/odoo/fields.py index 608adcfd045..3f32f68a765 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -15,7 +15,7 @@ import xmlrpclib import psycopg2 from odoo.sql_db import LazyCursor -from odoo.tools import float_precision, float_repr, float_round, frozendict, \ +from odoo.tools import float_repr, float_round, frozendict, \ html_sanitize, human_size, pg_varchar, ustr, OrderedSet from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT @@ -773,7 +773,7 @@ class Field(object): """ Return the null value for this field in the record format. """ return False - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): """ Convert ``value`` from the ``write`` format to the SQL format. """ if value is None or value == False: return None @@ -1177,7 +1177,7 @@ class Boolean(Field): type = 'boolean' column_type = ('bool', 'bool') - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): return bool(value) def convert_to_cache(self, value, record, validate=True): @@ -1198,7 +1198,7 @@ class Integer(Field): _description_group_operator = property(attrgetter('group_operator')) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): return int(value or 0) def convert_to_cache(self, value, record, validate=True): @@ -1262,7 +1262,7 @@ class Float(Field): _description_digits = property(attrgetter('digits')) _description_group_operator = property(attrgetter('group_operator')) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): result = float(value or 0.0) digits = self.digits if digits: @@ -1316,25 +1316,34 @@ class Monetary(Field): assert self.currency_field in model._fields, \ "Field %s with unknown currency_field %r" % (self, self.currency_field) - def convert_to_column(self, value, record): - try: - return value.float_repr() # see float_precision.float_repr() - except Exception: - return float(value or 0.0) + def convert_to_column(self, value, record, values=None): + # retrieve currency from values or record + if values and self.currency_field in values: + field = record._fields[self.currency_field] + currency = field.convert_to_cache(values[self.currency_field], record) + currency = field.convert_to_record(currency, record) + else: + # Note: this is wrong if 'record' is several records with different + # currencies, which is functional nonsense and should not happen + currency = record[:1][self.currency_field] + + value = float(value or 0.0) + if currency: + return float_repr(currency.round(value), currency.decimal_places) + return value def convert_to_cache(self, value, record, validate=True): - if validate: - currency = record[self.currency_field] + # cache format: float + value = float(value or 0.0) + if validate and record[self.currency_field]: # FIXME @rco-odoo: currency may not be already initialized if it is # a function or related field! - if currency: - value = currency.round(float(value or 0.0)) - return float_precision(value, currency.decimal_places) - return float(value or 0.0) + value = record[self.currency_field].round(value) + return value def convert_to_read(self, value, record, use_name_get=True): # float_precision values are not supported in pure XMLRPC - return float(value) + return value def convert_to_write(self, value, record): return value @@ -1435,7 +1444,7 @@ class Char(_String): assert isinstance(self.size, (NoneType, int)), \ "Char field %s with non-integer size %r" % (self, self.size) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): #TODO: # * we need to remove the "value==False" from the next line BUT # for now too many things rely on this broken behavior @@ -1504,7 +1513,7 @@ class Html(_String): _description_strip_style = property(attrgetter('strip_style')) _description_strip_classes = property(attrgetter('strip_classes')) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): if value is None or value is False: return None if self.sanitize: @@ -1685,7 +1694,7 @@ class Binary(Field): _description_attachment = property(attrgetter('attachment')) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): # Binary values may be byte strings (python 2.6 byte array), but # the legacy OpenERP convention is to transfer and store binaries # as base64-encoded strings. The base64 string may be provided as a @@ -1845,7 +1854,7 @@ class Selection(Field): return item[1] return False - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): """ Convert ``value`` from the ``write`` format to the SQL format. """ if value is None or value is False: return None @@ -2012,7 +2021,7 @@ class Many2one(_Relational): """ records._cache[self] = self.convert_to_cache(value, records, validate=False) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): return value or None def convert_to_cache(self, value, record, validate=True): diff --git a/odoo/models.py b/odoo/models.py index 8d049eae7e3..b3aec1441cc 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3072,7 +3072,7 @@ class BaseModel(object): if field.column_type: if single_lang or not (has_trans and field.translate is True): # val is not a translation: update the table - val = field.convert_to_column(val, self) + val = field.convert_to_column(val, self, vals) updates.append((name, field.column_format, val)) direct.append(name) else: @@ -3112,7 +3112,7 @@ class BaseModel(object): # Insert value to DB src_trans = vals[name] self.with_context(lang=None).write({name: src_trans}) - val = field.convert_to_column(vals[name], self) + val = field.convert_to_column(vals[name], self, vals) tname = "%s,%s" % (self._name, name) self.env['ir.translation']._set_ids( tname, 'model', self.env.lang, self.ids, val, src_trans) @@ -3336,7 +3336,7 @@ class BaseModel(object): parent_id = self.env[parent_model].create(parent_vals).id else: self.env[parent_model].browse(parent_id).write(parent_vals) - updates.append((self._inherits[parent_model], '%s', parent_id)) + vals[self._inherits[parent_model]] = parent_id # set boolean fields to False by default (to make search more powerful) for name, field in self._fields.iteritems(): @@ -3344,10 +3344,12 @@ class BaseModel(object): vals[name] = False # determine SQL values + self = self.browse() for name, val in vals.iteritems(): field = self._fields[name] if field.store and field.column_type: - updates.append((name, field.column_format, field.convert_to_column(val, self))) + column_val = field.convert_to_column(val, self, vals) + updates.append((name, field.column_format, column_val)) else: upd_todo.append(name) diff --git a/odoo/tools/float_utils.py b/odoo/tools/float_utils.py index 127c546125f..69c11148abe 100644 --- a/odoo/tools/float_utils.py +++ b/odoo/tools/float_utils.py @@ -176,24 +176,6 @@ def float_split(value, precision_digits): return int(units), int(cents) -class float_precision(float): - """ A class for float values that carry precision digits. This is a thin - layer on top of ``float``, and the precision digits are not propagated - to the result of arithmetic operations. This class is used when - converting monetary values to cache, and for serializing them to the - database. - """ - __slots__ = ['precision_digits'] - - def __new__(cls, value, precision_digits): - obj = super(float_precision, cls).__new__(cls, value) - obj.precision_digits = precision_digits - return obj - - def float_repr(self): - return _float_repr(self, self.precision_digits) - - if __name__ == "__main__": import time