Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] models: using unknown fields makes CRUD methods crash #37094

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion addons/account/models/account_reconcile_model.py
Expand Up @@ -147,7 +147,7 @@ def action_reconcile_stat(self):
return action

def _compute_number_entries(self):
data = self.env['account.move.line'].read_group([('reconcile_model_id', 'in', self.ids)], ['reconcile_model_ids'], 'reconcile_model_id')
data = self.env['account.move.line'].read_group([('reconcile_model_id', 'in', self.ids)], ['reconcile_model_id'], 'reconcile_model_id')
mapped_data = dict([(d['reconcile_model_id'][0], d['reconcile_model_id_count']) for d in data])
for model in self:
model.number_entries = mapped_data.get(model.id, 0)
Expand Down
2 changes: 0 additions & 2 deletions addons/hr_holidays/tests/test_holidays_flow.py
Expand Up @@ -26,7 +26,6 @@ def test_00_leave_request_flow_unlimited(self):
HolidayStatusManagerGroup.create({
'name': 'WithMeetingType',
'allocation_type': 'no',
'categ_id': self.env['calendar.event.type'].with_user(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id
})
self.holidays_status_hr = HolidayStatusManagerGroup.create({
'name': 'NotLimitedHR',
Expand Down Expand Up @@ -100,7 +99,6 @@ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
HolidayStatusManagerGroup.create({
'name': 'WithMeetingType',
'allocation_type': 'no',
'categ_id': self.env['calendar.event.type'].with_user(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

categ_id was removed by 22cd306 of #33233

'validity_start': False,
})

Expand Down
34 changes: 34 additions & 0 deletions odoo/addons/test_new_api/tests/test_new_fields.py
Expand Up @@ -61,6 +61,40 @@ def test_01_basic_set_assertion(self):
record.priority = 4
self.assertEqual(record.priority, 5)

def test_05_unknown_fields(self):
""" test ORM operations with unknown fields """
cat = self.env['test_new_api.category'].create({'name': 'Foo'})

with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.search([('zzz', '=', 42)])
with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.search([], order='zzz')

with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.read(['zzz'])

with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.read_group([('zzz', '=', 42)], fields=['color'], groupby=['parent'])
with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.read_group([], fields=['zzz'], groupby=['parent'])
with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.read_group([], fields=['zzz:sum'], groupby=['parent'])
with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.read_group([], fields=['color'], groupby=['zzz'])
with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.read_group([], fields=['color'], groupby=['parent'], orderby='zzz')
# exception: accept '__count' as field to aggregate
cat.read_group([], fields=['__count'], groupby=['parent'])

with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.create({'name': 'Foo', 'zzz': 42})

with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.write({'zzz': 42})

with self.assertRaisesRegex(ValueError, 'Invalid field'):
cat.new({'name': 'Foo', 'zzz': 42})

def test_10_computed(self):
""" check definition of computed fields """
# by default function fields are not stored and readonly
Expand Down
39 changes: 29 additions & 10 deletions odoo/models.py
Expand Up @@ -1890,6 +1890,8 @@ def _read_group_prepare(self, orderby, aggregated_fields, annotated_groupbys, qu
elif order_field in aggregated_fields:
order_split[0] = '"%s"' % order_field
orderby_terms.append(' '.join(order_split))
elif order_field not in self._fields:
raise ValueError("Invalid field %r on model %r" % (order_field, self._name))
else:
# Cannot order by a field that will not appear in the results (needs to be grouped or aggregated)
_logger.warn('%s: read_group order by `%s` ignored, cannot sort on empty columns (not grouped/aggregated)',
Expand All @@ -1904,7 +1906,10 @@ def _read_group_process_groupby(self, gb, query):
field name, type, time information, qualified name, ...
"""
split = gb.split(':')
field_type = self._fields[split[0]].type
field = self._fields.get(split[0])
if not field:
raise ValueError("Invalid field %r on model %r" % (split[0], self._name))
field_type = field.type
gb_function = split[1] if len(split) == 2 else None
temporal = field_type in ('date', 'datetime')
tz_convert = field_type == 'datetime' and self._context.get('tz') in pytz.all_timezones
Expand Down Expand Up @@ -2117,6 +2122,9 @@ def _read_group_raw(self, domain, fields, groupby, offset=0, limit=None, orderby
for fspec in fields:
if fspec == 'sequence':
continue
if fspec == '__count':
# the web client sometimes adds this pseudo-field in the list
continue

match = regex_field_agg.match(fspec)
if not match:
Expand All @@ -2126,15 +2134,19 @@ def _read_group_raw(self, domain, fields, groupby, offset=0, limit=None, orderby
if func:
# we have either 'name:func' or 'name:func(fname)'
fname = fname or name
field = self._fields[fname]
field = self._fields.get(fname)
if not field:
raise ValueError("Invalid field %r on model %r" % (fname, self._name))
if not (field.base_field.store and field.base_field.column_type):
raise UserError(_("Cannot aggregate field %r.") % fname)
if func not in VALID_AGGREGATE_FUNCTIONS:
raise UserError(_("Invalid aggregation function %r.") % func)
else:
# we have 'name', retrieve the aggregator on the field
field = self._fields.get(name)
if not (field and field.base_field.store and
if not field:
raise ValueError("Invalid field %r on model %r" % (name, self._name))
if not (field.base_field.store and
field.base_field.column_type and field.group_operator):
continue
func, fname = field.group_operator, name
Expand Down Expand Up @@ -2817,7 +2829,9 @@ def read(self, fields=None, load='_classic_read'):
# fetch stored fields from the database to the cache
stored_fields = set()
for name in fields:
field = self._fields[name]
field = self._fields.get(name)
if not field:
raise ValueError("Invalid field %r on model %r" % (name, self._name))
if field.store:
stored_fields.add(name)
elif field.compute:
Expand Down Expand Up @@ -3391,7 +3405,9 @@ def write(self, vals):
protected = set()
check_company = False
for fname in vals:
field = self._fields[fname]
field = self._fields.get(fname)
if not field:
raise ValueError("Invalid field %r on model %r" % (fname, self._name))
if field.inverse:
if field.type in ('one2many', 'many2many'):
# The written value is a list of commands that must applied
Expand Down Expand Up @@ -3605,8 +3621,7 @@ def create(self, vals_list):
continue
field = self._fields.get(key)
if not field:
_logger.warning("%s.create() with unknown fields: %s", self._name, key)
continue
raise ValueError("Invalid field %r on model %r" % (key, self._name))
if field.company_dependent:
irprop_def = self.env['ir.property'].get(key, self._name)
cached_def = field.convert_to_cache(irprop_def, self)
Expand Down Expand Up @@ -4161,7 +4176,7 @@ def _generate_order_by_inner(self, alias, order_spec, query, reverse_direction=F

field = self._fields.get(order_field)
if not field:
raise ValueError(_("Sorting field %s not found on model %s") % (order_field, self._name))
raise ValueError("Invalid field %r on model %r" % (order_field, self._name))

if order_field == 'id':
order_by_elements.append('"%s"."%s" %s' % (alias, order_field, order_direction))
Expand Down Expand Up @@ -4249,7 +4264,8 @@ def _flush_search(self, domain, fields=None, order=None):
order_spec = order or self._order
for order_part in order_spec.split(','):
order_field = order_part.split()[0]
to_flush[self._name].add(order_field)
if order_field in self._fields:
to_flush[self._name].add(order_field)

if 'active' in self:
to_flush[self._name].add('active')
Expand Down Expand Up @@ -4993,7 +5009,10 @@ def is_monetary(pair):
self.ensure_one()
cache = self.env.cache
fields = self._fields
field_values = [(fields[name], value) for name, value in values.items()]
try:
field_values = [(fields[name], value) for name, value in values.items()]
except KeyError as e:
raise ValueError("Invalid field %r on model %r" % (e.args[0], self._name))

# convert monetary fields last in order to ensure proper rounding
for field, value in sorted(field_values, key=is_monetary):
Expand Down