Parse equality to a date as two clauses when handling datetime fields
Closes #14778 (grafted from 6952308a8b6b65ff583a2b04d9cd558b44c4e4a9) --HG-- branch : 8.0
This commit is contained in:
@@ -1951,6 +1951,32 @@
|
|||||||
]);
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ((typeof value == 'string') &&
|
||||||
|
['datetime', 'timestamp'].includes(field.type) &&
|
||||||
|
(operator == '=')) {
|
||||||
|
let ctx, format_, parsed_date;
|
||||||
|
if (this.context && Object.keys(this.context).length) {
|
||||||
|
ctx = this.context;
|
||||||
|
} else {
|
||||||
|
ctx = {};
|
||||||
|
}
|
||||||
|
format_ = Sao.common.date_format(ctx.date_format);
|
||||||
|
parsed_date = Sao.common.parse_date(format_, value);
|
||||||
|
if (parsed_date &&
|
||||||
|
(Sao.common.format_date(format_, parsed_date) == value)) {
|
||||||
|
let date = Sao.DateTime.combine(parsed_date, Sao.Time());
|
||||||
|
let next_day = Sao.DateTime(
|
||||||
|
date.year(), date.month(), date.date(),
|
||||||
|
date.hour(), date.minute(), date.second(),
|
||||||
|
date.millisecond())
|
||||||
|
next_day.add(1, 'day');
|
||||||
|
result.push(this._clausify([
|
||||||
|
[field_name, '>=', date],
|
||||||
|
[field_name, '<', next_day]
|
||||||
|
]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (['many2one', 'one2many', 'many2many', 'one2one',
|
if (['many2one', 'one2many', 'many2many', 'one2one',
|
||||||
'many2many', 'one2one'].includes(field.type) && value) {
|
'many2many', 'one2one'].includes(field.type) && value) {
|
||||||
|
|||||||
@@ -1933,6 +1933,12 @@
|
|||||||
'name': 'integer',
|
'name': 'integer',
|
||||||
'type': 'integer'
|
'type': 'integer'
|
||||||
},
|
},
|
||||||
|
'timestamp': {
|
||||||
|
'name': 'timestamp',
|
||||||
|
'string': 'Timestamp',
|
||||||
|
'type': 'timestamp',
|
||||||
|
'format': '"%H:%M:%S"',
|
||||||
|
},
|
||||||
'selection': {
|
'selection': {
|
||||||
'string': 'Selection',
|
'string': 'Selection',
|
||||||
'name': 'selection',
|
'name': 'selection',
|
||||||
@@ -2033,6 +2039,7 @@
|
|||||||
c(['integer', '>=', 3]),
|
c(['integer', '>=', 3]),
|
||||||
c(['integer', '<=', 5])
|
c(['integer', '<=', 5])
|
||||||
]]],
|
]]],
|
||||||
|
[[c(['Timestamp', null, null])], [c(['timestamp', '=', null])]],
|
||||||
[[c(['Reference', null, 'foo'])],
|
[[c(['Reference', null, 'foo'])],
|
||||||
[c(['reference', 'ilike', '%foo%'])]],
|
[c(['reference', 'ilike', '%foo%'])]],
|
||||||
[[c(['Reference', null, 'Spam'])],
|
[[c(['Reference', null, 'Spam'])],
|
||||||
@@ -2073,6 +2080,33 @@
|
|||||||
QUnit.assert.deepEqual(parser.parse_clause(value), result,
|
QUnit.assert.deepEqual(parser.parse_clause(value), result,
|
||||||
'parse_clause(' + JSON.stringify(value) + ')');
|
'parse_clause(' + JSON.stringify(value) + ')');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let clause = parser.parse_clause([c(
|
||||||
|
['Timestamp', '=', Sao.common.format_date('%x', Sao.Date(2002, 12, 4))])]);
|
||||||
|
QUnit.assert.strictEqual(clause[0].length, 2);
|
||||||
|
let [ , operator, value] = clause[0][0];
|
||||||
|
QUnit.assert.strictEqual(operator, '>=');
|
||||||
|
QUnit.assert.ok(value.isSame(Sao.Date(2002, 12, 4)));
|
||||||
|
[ , operator, value] = clause[0][1];
|
||||||
|
QUnit.assert.strictEqual(operator, '<');
|
||||||
|
QUnit.assert.ok(value.isSame(Sao.Date(2002, 12, 5)));
|
||||||
|
|
||||||
|
clause = parser.parse_clause([c(
|
||||||
|
['Timestamp', '=',
|
||||||
|
Sao.common.format_datetime('%x %X', Sao.DateTime(2002, 12, 4, 12, 30))])]);
|
||||||
|
[ , operator, value] = clause[0];
|
||||||
|
QUnit.assert.strictEqual(operator, '=');
|
||||||
|
QUnit.assert.ok(value.isSame(Sao.DateTime(2002, 12, 4, 12, 30)));
|
||||||
|
|
||||||
|
clause = parser.parse_clause([c(
|
||||||
|
['Timestamp', null, [
|
||||||
|
`${Sao.common.format_date('%x', Sao.Date(2002, 12, 4))}`,
|
||||||
|
`${Sao.common.format_date('%x', Sao.Date(2002, 12, 5))}`,
|
||||||
|
]])]);
|
||||||
|
[ , operator, value] = clause[0];
|
||||||
|
QUnit.assert.strictEqual(operator, 'in');
|
||||||
|
QUnit.assert.ok(value[0].isSame(Sao.DateTime(2002, 12, 4)));
|
||||||
|
QUnit.assert.ok(value[1].isSame(Sao.DateTime(2002, 12, 5)));
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('DomainParser.format_value', function() {
|
QUnit.test('DomainParser.format_value', function() {
|
||||||
|
|||||||
@@ -885,6 +885,26 @@ class DomainParser(object):
|
|||||||
(field_name, '<=', rvalue),
|
(field_name, '<=', rvalue),
|
||||||
])
|
])
|
||||||
continue
|
continue
|
||||||
|
if (isinstance(value, str)
|
||||||
|
and field['type'] in {'datetime', 'timestamp'}
|
||||||
|
and operator == '='):
|
||||||
|
ctx = self.context if self.context else {}
|
||||||
|
format_ = date_format(ctx.get('date_format'))
|
||||||
|
try:
|
||||||
|
dt = datetime.datetime.strptime(
|
||||||
|
value, format_).date()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
dt = None
|
||||||
|
if dt:
|
||||||
|
date = untimezoned_date(
|
||||||
|
datetime.datetime.combine(
|
||||||
|
dt, datetime.time()))
|
||||||
|
next_date = date + datetime.timedelta(days=1)
|
||||||
|
yield iter([
|
||||||
|
(field_name, '>=', date),
|
||||||
|
(field_name, '<', next_date),
|
||||||
|
])
|
||||||
|
continue
|
||||||
if field['type'] in {
|
if field['type'] in {
|
||||||
'many2one', 'one2many', 'many2many', 'one2one',
|
'many2one', 'one2many', 'many2many', 'one2one',
|
||||||
} and value:
|
} and value:
|
||||||
|
|||||||
@@ -943,6 +943,12 @@ class DomainParserTestCase(TestCase):
|
|||||||
'name': 'integer',
|
'name': 'integer',
|
||||||
'type': 'integer',
|
'type': 'integer',
|
||||||
},
|
},
|
||||||
|
'timestamp': {
|
||||||
|
'name': 'timestamp',
|
||||||
|
'string': 'Timestamp',
|
||||||
|
'type': 'timestamp',
|
||||||
|
'format': '"%H:%M:%S"',
|
||||||
|
},
|
||||||
'selection': {
|
'selection': {
|
||||||
'string': 'Selection',
|
'string': 'Selection',
|
||||||
'name': 'selection',
|
'name': 'selection',
|
||||||
@@ -1161,6 +1167,33 @@ class DomainParserTestCase(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
rlist(dom.parse_clause(iter([['AND']]))),
|
rlist(dom.parse_clause(iter([['AND']]))),
|
||||||
[('rec_name', 'ilike', "%AND%")])
|
[('rec_name', 'ilike', "%AND%")])
|
||||||
|
for search_text, domain in [
|
||||||
|
([('Timestamp', None, None)], [('timestamp', '=', None)]),
|
||||||
|
(
|
||||||
|
[('Timestamp', '=', dt.date(2002, 12, 4).strftime('%x'))],
|
||||||
|
[[
|
||||||
|
('timestamp', '>=',
|
||||||
|
untimezoned_date(dt.datetime(2002, 12, 4))),
|
||||||
|
('timestamp', '<',
|
||||||
|
untimezoned_date(dt.datetime(2002, 12, 5)))]]),
|
||||||
|
(
|
||||||
|
[('Timestamp', '=',
|
||||||
|
dt.datetime(2002, 12, 4, 12, 30).strftime('%x %X'))
|
||||||
|
],
|
||||||
|
[('timestamp', '=', untimezoned_date(
|
||||||
|
dt.datetime(2002, 12, 4, 12, 30)))]),
|
||||||
|
(
|
||||||
|
[('Timestamp', None, [
|
||||||
|
f"{dt.date(2002, 12, 4).strftime('%x')}",
|
||||||
|
f"{dt.date(2002, 12, 5).strftime('%x')}",
|
||||||
|
])],
|
||||||
|
[('timestamp', 'in', [
|
||||||
|
untimezoned_date(dt.datetime(2002, 12, 4)),
|
||||||
|
untimezoned_date(dt.datetime(2002, 12, 5)),
|
||||||
|
])]),
|
||||||
|
]:
|
||||||
|
with self.subTest(f'parse_clause({search_text})'):
|
||||||
|
self.assertEqual(rlist(dom.parse_clause(search_text)), domain)
|
||||||
|
|
||||||
def test_completion_char(self):
|
def test_completion_char(self):
|
||||||
"Test completion char"
|
"Test completion char"
|
||||||
|
|||||||
Reference in New Issue
Block a user